Skip to content

Mocking HttpWebRequest by Microsoft Fakes

Akira Sugiura edited this page Apr 24, 2016 · 13 revisions

Let me introduce Fakes -> Prig migration, also I take the migration explanation that is listed in the section "Migrating from commercial and open source frameworks" of the article "Better Unit Testing with Microsoft Fakes" for example.

Testing target was like this(I modified it a little, because some warnings were occurring):

using System.Net;

namespace FakesMigration
{
    public class WebServiceClient
    {
        public bool CallWebService(string url)
        {
            var request = CreateWebRequest(url);
            var isValid = true;
            try
            {
                var response = request.GetResponse() as HttpWebResponse;
                isValid = HttpStatusCode.OK == response.StatusCode;
            }
            catch
            {
                isValid = false;
            }
            return isValid;
        }

        static HttpWebRequest CreateWebRequest(string url)
        {
            var request = WebRequest.Create(url) as HttpWebRequest;
            request.ContentType = "text/xml;charset=\"utf-8\"";
            request.Method = "GET";
            request.Timeout = 1000;
            request.Credentials = CredentialCache.DefaultNetworkCredentials;
            return request;
        }
    }
}

Personally, I can't approve the example, because it doesn't verify any inputs to the side-effect, and it doesn't contain its expected result in the test method name, and so on. Be that as it may, let's migrate it to Prig tentatively just as built successfully. Also, project's assembly references is same as the migration sample of Moles. For details, please see its document.

The classes that are called "Shim" of Fakes meet to "Indirection Stub" of Prig. By the naming rule, we can understand that the sample's classes which are used as "Shim" are HttpWebRequest, WebRequest and HttpWebResponse. These all classes are belonging in the assembly System. So, add the indirection setting from the context menu like the following image:

After the indirection setting System.v4.0.30319.v4.0.0.0.prig is added, paste the results made by running the following commands:

PM> $targets = [System.Net.HttpWebRequest].GetMembers() | ? { $_ -is [System.Reflection.MethodBase] } | ? { !$_.IsAbstract } | ? { $_.DeclaringType -eq $_.ReflectedType }
PM> $targets += [System.Net.WebRequest].GetMembers() | ? { $_ -is [System.Reflection.MethodBase] } | ? { !$_.IsAbstract } | ? { $_.DeclaringType -eq $_.ReflectedType }
PM> $targets += [System.Net.HttpWebResponse].GetMembers() | ? { $_ -is [System.Reflection.MethodBase] } | ? { !$_.IsAbstract } | ? { $_.DeclaringType -eq $_.ReflectedType }
PM> $targets | pget | clip

? { !$_.IsAbstract } is a filter to exclude the methods that have no implementation, and $_.DeclaringType -eq $_.ReflectedType is a filter to exclude the methods overriding base methods. I applied a very rough filter to explain, but I recommended that you make minimum settings by a strict filter. Making be replaceable extra methods on more than necessary isn't a good thing, because it has far-reaching ramifications.

If you got the build success against the pasted Indirection Stub Setting, I think that you can write a test like this:

using FakesMigration;
using NUnit.Framework;
using System.Net;
using System.Net.Prig;
using Urasandesu.Prig.Framework;

namespace FakesMigrationTest
{
    [TestFixture]
    public class WebServiceClientTest
    {
        [Test]
        public void TestThatServiceReturnsAForbiddenStatuscode()
        {
            // Use new IndirectionsContext() instead of ShimsContext.Create().
            using (new IndirectionsContext())
            {
                // Arrange 
                // Use the indirection stub which has name starting with "PProxy" against one instance.
                var requestProxy = new PProxyHttpWebRequest();
                // The indirection stubs "PProxy..." have implicit operator same as Fakes, so you can write as follows: 
                PWebRequest.CreateString().Body = uri => requestProxy;
                // Unlike Fakes, in Prig, "this" is implicitly passed as first parameter of the instance method.
                requestProxy.GetResponse().Body = this1 =>
                {
                    var responseProxy = new PProxyHttpWebResponse();
                    responseProxy.StatusCodeGet().Body = this2 => HttpStatusCode.Forbidden;
                    return responseProxy;
                };
                // Unlike Fakes, Prig tries invoking the original method by default.
                // If you want to make stubs be do-nothing, change the default behavior as follows: 
                requestProxy.ExcludeGeneric().DefaultBehavior = IndirectionBehaviors.DefaultValue;
                var client = new WebServiceClient();
                var url = "testService";
                var expectedResult = false;


                // Act 
                bool actualresult = client.CallWebService(url);


                // Assert 
                Assert.AreEqual(expectedResult, actualresult);
            }
        }
    }
}

By the way, about the article "Migrating from commercial and open source frameworks", I believe that the way to leave Moq untouched is easier because Fakes has no features as a Mock Object, Umm... 😕

APPENDIX: This example is corrected my personal unacceptable part by Moq.Prig:

using FakesMigration;
using Moq;
using NUnit.Framework;
using System.Net;
using System.Net.Prig;
using Urasandesu.Moq.Prig;
using Urasandesu.Moq.Prig.Mixins.Urasandesu.Prig.Framework;
using Urasandesu.Prig.Framework;

namespace FakesMigrationTest
{
    [TestFixture]
    public class WebServiceClientTest : TestBase
    {
        [Test]
        public void CallWebService_should_return_false_if_HttpStatusCode_is_Forbidden()
        {
            using (new IndirectionsContext())
            {
                // Arrange 
                var ms = new MockStorage(MockBehavior.Strict);

                var requestProxy = new PProxyHttpWebRequest();
                requestProxy.ExcludeGeneric().DefaultBehavior = IndirectionBehaviors.DefaultValue;

                var responseProxy = new PProxyHttpWebResponse();
                responseProxy.StatusCodeGet().Body = @this => HttpStatusCode.Forbidden;
                requestProxy.GetResponse().Body = @this => responseProxy;

                // To improve robustness against unintended modification, you should verify inputs to side-effects by Moq.
                // For example, the original test will pass even if you unintendedly changed the original production code as follows: 
                // var request = CreateWebRequest(url);
                //   -> var request = CreateWebRequest("Foo");
                var url = "testService";
                PWebRequest.CreateString().BodyBy(ms).Expect(_ => _(url)).Returns(requestProxy);


                // Act 
                var actual = new WebServiceClient().CallWebService(url);


                // Assert
                Assert.IsFalse(actual);
                ms.Verify();
            }
        }

        [Test]
        public void CallWebService_should_set_HttpWebRequest_to_request_textxml_content()
        {
            using (new IndirectionsContext())
            {
                // Arrange 
                var ms = new MockStorage(MockBehavior.Strict);

                // And also, you can verify whether HttpWebRequest is set intendedly if you use Moq.
                var requestProxy = new PProxyHttpWebRequest();
                requestProxy.ContentTypeSetString().BodyBy(ms).Expect(_ => _(requestProxy, "text/xml;charset=\"utf-8\""));
                requestProxy.MethodSetString().BodyBy(ms).Expect(_ => _(requestProxy, "GET"));
                requestProxy.TimeoutSetInt32().BodyBy(ms).Expect(_ => _(requestProxy, 1000));
                requestProxy.CredentialsSetICredentials().BodyBy(ms).Expect(_ => _(requestProxy, CredentialCache.DefaultNetworkCredentials));

                var responseProxy = new PProxyHttpWebResponse();
                responseProxy.StatusCodeGet().Body = @this => HttpStatusCode.OK;
                requestProxy.GetResponse().Body = @this => responseProxy;

                var url = "testService";
                PWebRequest.CreateString().Body = @this => requestProxy;


                // Act 
                var actual = new WebServiceClient().CallWebService(url);


                // Assert
                Assert.IsTrue(actual);
                ms.Verify();
            }
        }
    }
}

Complete source code is here.