Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Repetitive code in unit-tests

We find ourselves coding repetitive fixture/mock setups in many test-cases - like this case:

var fixture = new Fixture().Customize(new AutoMoqCustomization());
var encodingMock = fixture.Freeze<Mock<IEncodingWrapper>>();
var httpClientMock = fixture.Freeze<Mock<IHttpWebClientWrapper>>();
var httpResponseMock = fixture.Freeze<Mock<IHttpWebResponseWrapper>>();
var httpHeaderMock = fixture.Freeze<Mock<IHttpHeaderCollectionWrapper>>();
var etag = fixture.CreateAnonymous<string>();
byte[] data = fixture.CreateAnonymous<byte[]>();
Stream stream =  new MemoryStream(data);

encodingMock.Setup(m => m.GetBytes(It.IsAny<string>())).Returns(data);
httpHeaderMock.SetupGet(m => m[It.IsAny<string>()]).Returns(etag).Verifiable();
httpClientMock.Setup(m => m.GetResponse()).Returns(httpResponseMock.Object);
httpResponseMock.Setup(m => m.StatusCode).Returns(HttpStatusCode.OK);
httpResponseMock.SetupGet(m => m.Headers).Returns(httpHeaderMock.Object);
httpResponseMock.Setup(m => m.GetResponseStream()).Returns(stream);

As per the idea that the tests should be self-contained and readable from start to end we dont use magical Setup/Teardown methods.

Can we in any way (AutoFixture customizations, helper methods) reduce the "grunt work" of these tests?

like image 452
svrist Avatar asked May 08 '12 06:05

svrist


2 Answers

From Growing Object-Oriented Software (GOOS) comes a piece of good advice: if a test is hard to write, it's feedback about the API of the System Under Test (SUT). Consider redesigning the SUT. In this particular example, it looks as though the SUT has at least four dependencies, which might indicate a violation of the Single Responsibility Principle. Would it be possible to refactor to Facade Services?

Another great piece of advice from GOOS is that

  • Mocks should only be used for Commands
  • Stubs should be used for Queries

In the above example it looks as though you need to do a lot of Moq Setup for methods that are really Queries. That indicates a test smell as well. Is there a Law of Demeter violation somewhere? Would it be possible to cut the method chain?

like image 111
Mark Seemann Avatar answered Nov 04 '22 18:11

Mark Seemann


You can create a composite Customization that will customize the fixture by using all contained customizations.

public class HttpMocksCustomization : CompositeCustomization
{
    public HttpMocksCustomization()
        : base(
            new AutoMoqCustomization(),
            new HttpWebClientWrapperMockCustomization(),
            new HttpWebResponseWrapperMockCustomization()
            // ...
            )
    {
    }
}

Each customization can be defined as follow:

public class HttpWebClientWrapperMockCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        var mock = new Mock<IHttpWebClientWrapper>();
        mock.Setup(m => m.GetResponse()).Returns(httpResponseMock.Object);

        fixture.Inject(mock);
    }
}

public class HttpWebResponseWrapperMockCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        var mock = new Mock<IHttpWebResponseWrapper>();
        mock.Setup(m => m.StatusCode).Returns(HttpStatusCode.OK);

        fixture.Inject(mock);
    }
}

// The rest of the Customizations.

Then inside the test method you can do this:

var fixture = new Fixture().Customize(new HttpMocksCustomization());

That way, when you request a Mock instance you don't have to repeat the setup steps. The one we customized earlier will be returned:

var httpClientMock = fixture.Freeze<Mock<IHttpWebClientWrapper>>();

However, if you use xUnit.net, things can be simplified even further.

You can create an AutoDataAttribute-derived type to provide auto-generated data specimens generated by AutoFixture as an extention to xUnit.net's Theory attribute:

public class AutoHttpMocksDataAttribute : AutoDataAttribute
{
    public AutoHttpMocksDataAttribute()
        : base(new Fixture().Customize(new HttpMocksCustomization()))
    {
    }
}

Then, in your test method you can pass the Mocks as arguments:

[Theory, AutoHttpMocksData]
public void MyTestMethod([Freeze]Mock<IHttpWebClientWrapper> httpClientMock, [Freeze]Mock<IHttpWebResponseWrapper> httpResponseMock)
{
    // ...
} 
like image 24
Nikos Baxevanis Avatar answered Nov 04 '22 16:11

Nikos Baxevanis