With Moq in C#, how would you automate writing a MakeSut(out ...) method from any constructor?

37 Views Asked by At

Given any constructor of a class I want to test, such as:

public MyService(IFooStore store, IBarService service, IModeratelyLongDependencyName dependency3, ...)
{
    ...
}

Can you think of a good way to make a tool write a MakeSut method like the following, gives us mocks (using Moq) based on the constructor? I think this is the most tedious part of writing tests for me.

private static MyService MakeSut(
    out Mock<IFooStore> mockStore,
    out Mock<IBarService> mockService,
    out Mock<IModeratelyLongDependencyName> mockDependency3,
    ... )
{ 
  mockStore = new();
  mockService = new();
  mockDependency3 = new();

  return new MyService (
      mockStore.Object,
      mockService.Object,
      mockDependency3.Object,
      ...
  );

I have found this to be a good pattern, as the out-parameters let me Setup what I want and ignore what I don't care about when doing non-strict tests. Having this method with the out parameters lets me instantiate all manner of different SUTs easily like...

// arrange
MyService sut = MakeSut(out var mockStore,  out _, out _, ...);
mockStore.Setup(s => s.GetFoo(It.IsAny<int>())).ReturnsAsync(new Foo(){...});

// act
sut.DoTheThing();

// assert
mockStore.Verify(...);

I've explored doing this using Resharper's Custom Patterns, but it seems too limited to accomplish such a thing. So how would you go about writing something that could generate the code for such a MakeSut method from any constructor?

(Or am I barking up the wrong tree and you know of a better pattern to instantiate SUTs with all dependencies mocked and every mock available for .Setup?)

1

There are 1 best solutions below

2
On

I've been around that block a couple of times, and if you'd take my advice, then leave the test code tedious. Writing unit tests is one of the best and most immediate kinds of feedback you can get about the design of your System Under Test (SUT), so if the tests are tedious to write and maintain, it means that the SUT is hard to use and maintain.

With that out of the way, if I had to do something about such a situation today, I'd follow the Test Data Builder pattern, just applied to SUT construction instead of test data.

Another alternative, if you want to go all the way, you can create or adopt an Auto-mocking Container.

But the best solution is to simplify the SUT API so that it takes fewer dependencies. That makes the SUT easier to use and maintain - and test.