6
\$\begingroup\$

I am unit testing that the URL that I give to my IRestClient is valid. This client talks to a third party Web API.

The test is as below:

    [Test]
    [ExpectedException("System.UriFormatException")]
    public void MakeRequest_EndPointIsInValid_ThrowsUriFormatException()
    {
        var stub = new Mock<IRestClient>();
        stub.Setup(x => x.MakeRequest()).Throws<UriFormatException>();

        string results = new RestClient("example", "method=payment&params[Key1]=Value1&params[Key2]=Value2&params[Key3]=Value3").MakeRequest();
    }

The RestClient class under test is

public class RestClient : IRestClient
{
    /// <summary>
    /// Web Api Url End point.
    /// </summary>
    public string EndPoint { get; set; }

    /// <summary>
    /// The Post data.
    /// </summary>
    public string PostData { get; set; }

    /// <summary>
    /// Do Request.
    /// </summary>
    /// <returns></returns>
    public string MakeRequest()
    {
        using (WebClient client = new WebClient())
        {
            client.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";

            if (string.IsNullOrWhiteSpace(PostData)) throw new ArgumentNullException("PostData");
            if (string.IsNullOrWhiteSpace(EndPoint)) throw new ArgumentNullException("EndPoint");

            // I want to test this Uri parsing.
            Uri uriResult;
            if (!Uri.TryCreate(EndPoint, UriKind.Absolute, out uriResult) || uriResult.Scheme != Uri.UriSchemeHttps) throw new UriFormatException("EndPoint");

            return client.UploadString(uriResult, PostData);
        }
    }
}
  1. How is this test and class under test?

  2. Is my fake object a stub or a mock? I believe it's a stub as I am just forcing the return of a UriFormatException.

  3. Any improvements?

\$\endgroup\$
  • \$\begingroup\$ Does that code even compile? You are passing in constructor arguments to the RestClient but have no matching constructor defined? \$\endgroup\$ – dreza Jul 3 '13 at 9:53
  • \$\begingroup\$ I omitted the constructor for brevity. It just sets the two properties. \$\endgroup\$ – Sam Leach Jul 3 '13 at 10:03
  • \$\begingroup\$ Is the test actually passing? It seems to be you are using two different objects, one mocked, and one sut (service under test), and they are unrelated. Of course, I haven't used Moq before so maybe I'm missing something. \$\endgroup\$ – Jeff Vanzella Jul 3 '13 at 16:37
4
\$\begingroup\$

I think you are confused about what a mock is actually supposed to be used for. A mock is to create a fake object to pass into a parameter, and is very useful when testing systems that use dependency injection exclusively.

Take this for example:

public interface IFoo
{
     string SomeMethod(IBar bar);
}


public interfact IBar
{
    int CalculateSomething();
}


public class Foo : IFoo
{
     public string SomeMethod(IBar bar)
     {
         if (bar.CalculateSomething() > 10)
         {
              // insert more code

              return "Completed";
         }

         return string.Empty;
     }
 }

One of your tests is to ensure the // insert more code section works. To get into that code you need the IBar.CalculateSomething() to return a value > 10. You also don't want to create an instance of the Bar class, because then you are not testing one unit of code. This is where mocking comes into play. The way to make sure you hit the code is to pass a mock into the method, that is set up to pass the if statement

[Test]
public void EnsureThatInserMoreCodeWorks()
{
    var bar = Mock.Create<IBar>();
    bar.SetUp(b => b.CalculateSomething()).Returns(15);

    var sut = new Foo();

    var result = sut.SomeMethod(bar.Object);

    Assert.That(result, Is.EqualTo("Completed");
}

This is really simple, and I hope there is more to the // insert more code section to test, but this gives an easy to follow example of using mocks.

To make your test pass, you would simply have to pass in an invalid uri string. No Mock needed.

\$\endgroup\$
  • \$\begingroup\$ Good point Jeff \$\endgroup\$ – dreza Jul 3 '13 at 20:20
  • \$\begingroup\$ Thanks - "A mock is to create a fake object to pass into a parameter". This helped me wrap my head around this a bit more. \$\endgroup\$ – Sam Leach Jul 4 '13 at 7:35
  • \$\begingroup\$ One more question; semantically, is the fake a mock or a stub? \$\endgroup\$ – Sam Leach Jul 4 '13 at 7:38
2
\$\begingroup\$

I think I agree with Jeff. So because of this, perhaps you might want to reconsider the design? Do you really want to make RestClient dependant on WebClient or could we make it dependent on an interface instead and decouple it from that side of things? Perhaps this is overkill?

Here's a crack at an alternative for comments:

The testing

[TestMethod]
[ExpectedException(typeof(System.UriFormatException))]
public void Uri_InvalidFormatTest()
{
    var requestMock = new Mock<IRequestClient>();            
    var client = new RestClient(requestMock.Object);

    string address = "example";
    string postData = "method=payment&params[Key1]=Value1&params[Key2]=Value2&params[Key3]=Value3";

    client.MakeRequest(address, postData);
}

[TestMethod]
    public void Uri_ValidFormatTest()
    {
        var requestMock = new Mock<IRequestClient>();            
        var client = new RestClient(requestMock.Object);

        string address = "https://www.google.co.nz";
        string postData = "method=payment&params[Key1]=Value1&params[Key2]=Value2&params[Key3]=Value3";

        requestMock.Setup(x => x.UploadString(new Uri(address), postData)).Returns("Success");

        Assert.AreEqual("Success", client.MakeRequest(address, postData));
    }

The abstraction

public interface IRestClient
{
    string MakeRequest(string endPoint, string postData);
}

public interface IRequestClient
{
    void ContentType(string contentType);
    string UploadString(Uri address, string postData);
}

The implementation

public class RequestClient: IRequestClient
{
    private string _contentType;

    public void ContentType(string contentType)
    {
        _contentType = contentType;
    }

    public string UploadString(Uri address, string postData)
    {
        using (WebClient client = new WebClient())
        {
            if (!string.IsNullOrWhiteSpace(_contentType))
            {
                client.Headers[HttpRequestHeader.ContentType] = _contentType;
            }

            return client.UploadString(address, postData);
        }
    }
}

public class RestClient : IRestClient
{
    private readonly IRequestClient _client;

    public RestClient(IRequestClient client)
    {
        _client = client;
    }

    /// <summary>
    /// Do Request.
    /// </summary>
    /// <returns></returns>
    public string MakeRequest(string endPoint, string postData)
    {
        if (string.IsNullOrWhiteSpace(postData)) throw new ArgumentNullException("postData");
        if (string.IsNullOrWhiteSpace(endPoint)) throw new ArgumentNullException("endPoint");

        Uri uriResult;
        if (!Uri.TryCreate(endPoint, UriKind.Absolute, out uriResult) || uriResult.Scheme != Uri.UriSchemeHttps)
        {
            throw new UriFormatException("EndPoint");
        }

        _client.ContentType("application/x-www-form-urlencoded");
        return _client.UploadString(uriResult, postData);
    }
}
\$\endgroup\$
  • \$\begingroup\$ Thanks for your clear answer. To confirm, we are testing RestClient's interaction with RequestClient? We are forcing the RequestClient to always return "Success" and we are testing that RestClient behaves correctly under this situation? \$\endgroup\$ – Sam Leach Jul 4 '13 at 7:34
  • \$\begingroup\$ In the cases above we would be testing MakeRequests functionality. By mocking the IRestClient you then ensure you don't have to worry about it's implementation in order to test RestClient. Hence you could of coded RestClient first, put in a test then coded RequestClient knowning RestClient was already test? Hope tha makes sense :) \$\endgroup\$ – dreza Jul 5 '13 at 20:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.