سؤال

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?

هل كانت مفيدة؟

المحلول

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)
{
    // ...
} 

نصائح أخرى

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

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?

If all of your tests use this code, it should be placed in the set up/tear down methods. It's ok if your set up/tear down methods are somewhat complicated, as long as all of your unit tests are depending on it. This is certainly better than duplicating all that complicated stuff in every test. When I read a test, I know that setup and teardown are part of each test implicitly, so I don't think you lose anything in readability either. The thing to avoid is including things in setup that not every test needs. This creates confusing situations where you setup method doesn't match all your tests nicely. Ideally your set up method should apply 100% to every single test.

If the shared code is not used in all your tests, extract the shared code into helper functions.Writing good tests code is just like writing any other good code, and the same principles apply.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top