Creating Your Own Fakes Instead of Using Mocking Libraries

12/08/2023
C#.NETMoq

With respect to the current topic around Moq, I want to showcase how you can easily roll out your own fakes so that you are not depending on a third party library.¨

Mocking libraries

Mocking libraries like Moq, NSubstitute and friends are a popular choice when it comes to unit testing. They allow you to create fake objects that you can use to replace dependencies of the class under test. The goto choice for unit tests, but you can also roll out your fakes without using a mocking library. There are some major advantages to that:

  1. No 3rd party dependency: Less dependency on an external library means less risk of breaking changes or other issues (see the current Moq problem).
  2. Transparency: Rolling out your own fakes, makes it super obvious what's going to happen in one single place. No magic is involved!
  3. Flexibility: You can easily extend your fakes to your needs. No need to learn a new library or to find a workaround for a missing feature.

Obviously there are also some drawbacks I will showcase later. Let's come to the good part.

Create your own fakes

Before I show you some code, I want to explain some of the terms that fly around like fake, mock, shim or stub.

  • Fake is a general term and can include stubs, mocks, and shims.
  • Stub provides pre-set answers to calls.
  • Mock checks interactions and provides pre-set answers.
  • Shim diverts or changes the behavior of existing calls.

The more dominant ones are stub and mock. If you are coming from Moq

var mock = new Mock<IFoo>();
mock.Setup(foo => foo.DoSomething("ping")).Returns(true); // That is a stub
mock.Verify(foo => foo.DoSomething("ping")); // That is a mock

The same in NSubstitute

var substitute = Substitute.For<IFoo>();
substitute.DoSomething("ping").Returns(true); // That is a stub
substitute.Received(1).DoSomething("ping"); // That is a mock

Let's create our first fake. We want to fake the following interface:

public interface IDataService
{
    ValueTask<string> GetDataAsync(int id);
}

With moq, you would have done something like this:

var mock = new Mock<IDataService>();
mock.Setup(x => x.GetDataAsync(42)).ReturnsAsync("42");

With your own fake, you would do something like this:

public class FakeDataService : IDataService
{
    public ValueTask<string> GetDataAsync(int id)
    {
        return new ValueTask<string>("42");
    }
}

Imagine we have the following code that uses the IDataService:

public class UserProfileService
{
    private readonly IDataService _dataService;

    public UserProfileService(IDataService dataService)
    {
        _dataService = dataService;
    }

    public string GetUserProfileSummary(int userId)
    {
        var data = _dataService.GetData(userId);
        return $"Profile Summary for User {userId}: {data}";
    }
}

The usage is also pretty straightforward:

[Fact]
public void GetUserProfileSummary()
{
    // Arrange
    var fakeDataService = new FakeDataService();
    var userProfileService = new UserProfileService(fakeDataService);

    // Act
    var result = userProfileService.GetUserProfileSummary(42);

    // Assert
    Assert.Equal("Profile Summary for User 42: 42", result);
}

Super straightforward! As it is your own fake, you can easily adjust the behavior. For example, if you need to dynamically set the return value you could do this:

public class FakeDataService : IDataService
{
    public string ReturnData { get; set; }

    public ValueTask<string> GetDataAsync(int id)
    {
        return new ValueTask<string>(ReturnData);
    }
}

Another example would be to react to specific input values:

public class FakeDataService : IDataService
{
    public ValueTask<string> GetDataAsync(int id)
    {
        if (id == 42)
        {
            return new ValueTask<string>("42");
        }

        return new ValueTask<string>("Not 42");
    }
}

And the code is super easy to read and understand. Another use case of those famous libraries is to verify that certain calls were made. Let's say we want to verify that the GetDataAsync method was called with the value 42. With Moq, you would do something like this:

mock.Verify(x => x.GetDataAsync(42), Times.Once);

With your own fake, you would do something like this:

public class FakeDataService : IDataService
{
    public int GetDataAsyncCallCount { get; private set; }

    public ValueTask<string> GetDataAsync(int id)
    {
        GetDataAsyncCallCount++;
        return new ValueTask<string>("42");
    }
}

And in the test, you can check the GetDataAsyncCallCount property:

[Fact]
public void GetUserProfileSummary()
{
    // Arrange
    var fakeDataService = new FakeDataService();
    var userProfileService = new UserProfileService(fakeDataService);

    // Act
    var result = userProfileService.GetUserProfileSummary(42);

    // Assert
    Assert.Equal("Profile Summary for User 42: 42", result);
    Assert.Equal(1, fakeDataService.GetDataAsyncCallCount);
}

Obviously, that is an oversimplified example, and in this case, you wouldn't even need to assert the invocation count. But you get the idea.

Drawbacks and limitations

These are simple examples, and the real world is different from that. Creating your own fakes comes with a cost. You have to maintain them, and it can get tricky with more complex objects. On the plus side, if your fakes become very hard to write, there might also be a problem with your design - so it can be a nice indicator for coupling and complexity.

Another drawback is that you have to create your own fakes for every dependency you want to fake. That can be a lot of work. Last, but not least, there is a technical "problem" attached to real classes in C#. Let's assume we extend IDataService with a new method like GetMoreDataAsync. As your class directly derives from IDataService you have to implement that method. That can be a lot of work if you have a lot of fakes. My general recommendation is to have your fakes, that are used often, in some central place so you don't have to touch 100 places at once. Mocking libraries don't come with that problem, as they are creating a proxy at runtime. But then again, you make such problems explicit instead of hiding them.

Conclusion

I hope I could show you that creating your own fakes is not that hard and can be a good alternative to mocking libraries. It's not a silver bullet, and you have to decide for yourself if it's worth the effort. I personally like the transparency and flexibility of my own fakes.

3
An error has occurred. This application may no longer respond until reloaded. Reload x