Friday, March 10, 2017

How to use mocking framework in tests

Let's consider cases, when you may, and when you should not use mocking framework. Also here I'll show some patterns, you may want to use with mocking framework.



If you don't care...

Sometimes class you want to test (system under test, SUT) requires some dependencies. But it can happen, that your tests do not actually rely on work of these dependencies. In the case if you are content with default values returned from all members of a dependency, you may use mocking framework to create it:

var userSettingsService = new Mock<IUserSettingsService>().Object;
 
_controller = new SettingsController(userSettingsService);

But even in this case...

... limit usage of the mocking framework

Avoid situations when each test of your test class contains code, related to the mocking framework. It will make refactoring of code more difficult. Try to concentrate all such code inside test initialize and test cleanup methods.
There are times when it is not possible. For example, nearly any of your tests may require setting behaviour of some method of your dependency interface. Here you have several options:

  • Configuration method
In your test class you may create helper methods which will help with configuration of expectations:

private void UserIsNotAuthenticated(Mock<IAuthenticationService> authenticationService)
{
    authenticationService.Setup(s => s.IsAuthenticated(It.IsAny<HttpRequestBase>())).Returns(false);
}

Now you can use these methods in your tests instead of using the mocking framework directly:

UserIsNotAuthenticated(_authenticationService);
 
var result = (RedirectResult)_controller.Index(new LogonModel
{
    ReturnUrl = FakeUrl
});
 
Assert.AreEqual(FakeUrl, result.Url);

It will reduce impact of the mocking framework on your tests, and simplify refactoring.

  • Preconfigured mocks
Configuration methods can help if you use them in one test class. But if you need to define behaviour of some method in several test classes, there is another possible approach. You can create preconfigured mocks:

internal class AuthenticationServiceStub : Mock<IAuthenticationService>
{
    public AuthenticationServiceStub()
    {
        Setup(s => s.GetUserId(It.IsAny<string>())).Returns(1);
    }
 
    public void SetIsPasswordKeyValidTo(bool value)
    {
        Setup(s => s.IsPasswordKeyValid(It.IsAny<string>())).Returns(value);
    }
 
    // other code
}

In this case, you manually create class of mock, and derive it from class of the mocking framework. Here you can incapsulate common configuration for all instances (in constructor), and also define helper methods with meaningfull names to configure behaviour of separate members.
It is very simple to use such preconfigured mocks:

[TestInitialize]
public void TestInitialize()
{
    _authenticationService = new AuthenticationServiceStub();
    _controller = new PasswordController(_authenticationService.Object);
}

as well as its configuration methods:

_authenticationService.SetIsPasswordKeyValidTo(false);
 
var result = (ViewResult)_controller.Index(Constants.Domain, "invalidKey");
 
Assert.AreEqual("Error", result.ViewName);

Complex behaviour of mocks

There are cases when you want complex behaviour from your mocks. For example, you could want several methods of some interface to work together and provide consistent information. Although it is possible to achieve it using the mocking framework, I advice you to resist this temptation. Implement such mocks manually:

public class PermissionsProviderStub : IPermissionsProvider
{
    internal const string NoPermissions = "NoPermissions";
    internal const string ReadPermission = "ReadPermission";
 
    public Permissions GetPermissions(string objectId, int userId)
    {
        if (objectId.Equals(NoPermissions))
            return new NoPermissions();
 
        if (objectId.Equals(ReadPermission))
            return new Permissions { HasReadAccess = true };
 
        throw new ArgumentException($"Stub does not suport ObjectId: {objectId} not supported");
    }
}

It is much cleaner and easier to understand.

Nevertheless, even in this case the mocking framework can help you...

Partial implementation

There are interfaces with a very long list of members. I know, that in many situations we should break up such interfaces into smaller ones. But still they exist. You may want to implement complex behaviour for couple of its members, and you don't care about the other members. In this case, you can use partial implementation of the interface:

public class LanguageProviderStub : Mock<ILanguageProvider>
{
    public LanguageProviderStub()
    {
        Setup(p => p.GetPreferredLanguage(It.IsAny<int>(), It.IsAny<List<int>>(), It.IsAny<int>()))
            .Returns((Func<intList<int>, intLang>)GetPreferredLanguage);
 
        Setup(p => p.GetPreferredLanguageId(It.IsAny<int>(), It.IsAny<List<int>>(), It.IsAny<int>()))
            .Returns((Func<intList<int>, intint>)GetPreferredLanguageId);
    }
 
    private Lang GetPreferredLanguage(int language, List<int> available, int defaultLanguage)
    {
        if (available.Contains(language))
            return new Lang(language);
 
        return new Lang(defaultLanguage);
    }
 
    private int GetPreferredLanguageId(int language, List<int> available, int defaultLanguage)
    {
        var lang = GetPreferredLanguage(language, available, defaultLanguage);
 
        return lang != null ? lang.LangId : -1;
    }
}

You assign your implementation to the choosen methods, and make the mocking framework to take care of the rest:

var controller = new LanguageController(new LanguageProviderStub().Object);

Refactoring

I want to draw your attention to the fact, that using mocking framework does not mean you should not refactor the code of your tests. You may start with simple usage of the mocking framework for some interface. But later, as your tests evolve and grow, you may consider writing configuration methods and even preconfigured mocks. The way you use the mocking framework now is not written in stone, it may require changes in the future.

Conclusion

Let me summarize information in this article.
  • Choose your mocking framework wisely.
  • Use it if you content with default values of mocked members.
  • Limit usage of the framework: use configuration methods and preconfigured mocks.
  • Implement complex behaviour manually. In some cases partial implementation is allowed.
  • Refactoring is still mandatory.

No comments:

Post a Comment