Blog for Junior Developers C#/.NET

Wednesday, July 10, 2024

Today I will discuss a very important topic, without knowing which, you will not learn how to write good unit tests. As you have read in previous articles, unit tests should not touch external resources. So how to test the logic in methods that have references to external resources? You have to use artificial objects, so-called mocks.

how-to-get-rid-of-external-dependencies-in-unit-tests-introduction-to-mocking-data-in-csharp.jpg

What is mocking?


Mocking, i.e. imitating something or some behavior. In Polish translation, you can find various translations of the word mock, including mock, but I will simply use mock. If a method has logic in it and uses external resources (such as a database, file, web services, etc.), then such a method would not be able to be tested in our unit tests. However, if we replace an external resource with an artificial object that has no logic, then we will be able to test such a method. For this to be successful, our code must follow certain rules. Among other things, it must have loose connections and operate on interfaces. If we follow these rules, we can replace the implementations for testing purposes. Therefore, if we want to add tests to some applications that were not written with tests in mind, this is where a problem may arise. We need to refactor the code, which can be a big challenge without prior testing :)


C# mocking frameworks


We have many different mocking frameworks to choose from, including Moq, NSubstitute, FakeItEasy, Rhino Mocks and many others. I think it is worth taking an interest in the Moq framework, which I use every day and I recommend it to you as well. It has all the functionality I need for mocking objects and is easy to use.


Code with external dependencies in C#


It will be easiest to learn how to mock using a specific example. Let's assume we want to test the application login method. To simplify the example, let's say that the method returns a message for bad data and an empty string for good data. Our class might look like this:

public interface IUsersRepository
{
    bool Login(string user, string password);
}

public class Authentication
{
    private readonly IUsersRepository _usersRepository;

    public Authentication(IUsersRepository usersRepository)
    {
        _usersRepository = usersRepository;
    }

    public string Login(string user, string password)
    {
        var isAuthenticated = _usersRepository.Login(user, password);//data from database

        if (!isAuthenticated)
            return "User or password is incorrect.";

        return string.Empty;
    }
}


We have an Authentication class whose constructor accepts an object of a class implementing the IUserRepository interface, this class is responsible for logging in. The Authentication class has one Login method, which returns an appropriate message based on whether we are successful in logging in. If the login data are correct, an empty string will be returned, otherwise a message that the data is incorrect.


Unit test example, with external dependencies in C#


If we wanted to check in a unit test whether we would actually receive the appropriate message, we would have to use an external resource (database) and then our tests would no longer be unit tests. It's good that the above code was written by a good programmer :) who knew that it was necessary to operate on interfaces so that the class was testable. We can replace the IUsersRepository implementation and use a mock interface instead. Our test code might look like this:

public class AuthenticationTests
{
    [Test]
    public void Login_WhenIncorrectData_ShouldReturnCorrectMessage()
    {
        //Arrange
        var mockUserRepository = new Mock<IUsersRepository>();
        mockUserRepository.Setup(x => x.Login("user", "password")).Returns(false);
        var authentication = new Authentication(mockUserRepository.Object);

        //Act
        var result = authentication.Login("user", "password");

        //Assert
        Assert.That(result, Does.Contain("User or password is incorrect"));
    }
}


First, we need to install the testing framework. You need to search through Manage NuGet Package - Moq, then install it.

As you can see in the first line of arrange, the Moq framework is used to initialize a mock object. Then we define how the Login method of our object should work. That is, we say that after calling the Login method for specific parameters, in our case login - user, and password - password, return false. In the next line, we pass the mock object as a parameter of the Authentication class. We call the login method and check whether the result contains the given message. Our unit test passes correctly, we do not refer to any external dependencies, and we managed to test the logic contained in this test.

Notice how important it is to operate on the interface in this case. If the Authentication class, instead of operating on the IUserRepository interface, worked on a regular class, it would not be possible to use mocks, and therefore unit tests.

Let's see what an example might look like when the user provides the correct password and login, then an empty string should be returned.

[Test]
public void Login_WhenCorrectData_ShouldReturnEmptyString()
{
    //Arrange
    var mockUserRepository = new Mock<IUsersRepository>();
    mockUserRepository.Setup(x => x.Login("user", "password")).Returns(true);
    var authentication = new Authentication(mockUserRepository.Object);

    //Act
    var result = authentication.Login("user", "password");

    //Assert
    Assert.That(result, Is.Empty);
}


In this example, in the configuration of the Login method we say that we want our method to return true if it is called with the user and password parameters. Thanks to this, we can test the path that interests us.

Note that if we call the Login method with different parameters in act, our test will be red. In arrange, we have configured the Login method to return true only for the user and password parameters; if the parameters are different, then the default value, i.e. false, will be returned. See this in an example:

[Test]
public void Login_WhenCorrectData_ShouldReturnEmptyString()
{
    //Arrange
    var mockUserRepository = new Mock<IUsersRepository>();
    mockUserRepository.Setup(x => x.Login("user", "password")).Returns(true);
    var authentication = new Authentication(mockUserRepository.Object);

    //Act
    var result = authentication.Login("1", "2");

    //Assert
    Assert.That(result, Is.Empty);
}


 This code doesn't go through. We receive the message: "Expected: But was: User or password is incorrect". We can also configure the call of this method for different parameters, thanks to It.IsAny<string>() this method can return the same result for all strings. This is what the whole implementation looks like:

[Test]
public void Login_WhenCorrectData_ShouldReturnEmptyString()
{
    //Arrange
    var mockUserRepository = new Mock<IUsersRepository>();
    mockUserRepository.Setup(x => x.Login(It.IsAny<string>(), It.IsAny<string>())).Returns(true);
    var authentication = new Authentication(mockUserRepository.Object);

    //Act
    var result = authentication.Login("1", "2");

    //Assert
    Assert.That(result, Is.Empty);
}


The test result is green. Thanks to this configuration, no matter what parameters the attached method is called in act, true will be returned for each string parameter.

Of course, we also need to test the login method that we are mocking in this case, but we will do it in integration tests, where we will check logging in on a real database. In this example, we want to test the remaining code, i.e. the logic in the Authentication class, and for this we need to get rid of external dependencies.


Verifying mock calls


Thanks to mocks, we can also verify how many times a mocked method was called, or whether it was called at all. To represent this, I'll change the test class a bit.

public interface IUsersRepository
{
    bool Login(string user, string password);
    void UpdateLastLoginDate(string user);
}

public class Authentication
{
    private readonly IUsersRepository _usersRepository;

    public Authentication(IUsersRepository usersRepository)
    {
        _usersRepository = usersRepository;
    }

    public string Login(string user, string password)
    {
        var isAuthenticated = _usersRepository.Login(user, password);//data from database

        if (!isAuthenticated)
            return "User or password is incorrect.";
       
        _usersRepository.UpdateLastLoginDate(user);
        return string.Empty;
    }
}


The UpdateLastLoginDate(string user) method has been added to the IUserRepository interface, which updates the last login date for a specific user. In the Login method, the UpdateLastLoginDate method should only be called when the user logs in. Let's check if this actually happens:

[Test]
public void Login_WhenCorrectData_ShouldUpdateLastLoginDate()
{
    //Arrange
    var mockUserRepository = new Mock<IUsersRepository>();
    mockUserRepository.Setup(x => x.Login("user", "password")).Returns(true);
    mockUserRepository.Setup(x => x.UpdateLastLoginDate("user"));
    var authentication = new Authentication(mockUserRepository.Object);

    //Act
    var result = authentication.Login("user", "password");

    //Assert
    mockUserRepository.Verify(x => x.UpdateLastLoginDate("user"), Times.Once);
}


 Thanks to the Verify method, we can check how many times a given method has been called. In the above case, we were interested in ensuring that the Login method was called exactly once, and this actually happened. You might as well check that a given method has not been called even once:

[Test]
public void Login_WhenIncorrectData_ShouldNotUpdateLastLoginDate()
{
    //Arrange
    var mockUserRepository = new Mock<IUsersRepository>();
    mockUserRepository.Setup(x => x.Login("user", "password")).Returns(false);
    mockUserRepository.Setup(x => x.UpdateLastLoginDate("user"));
    var authentication = new Authentication(mockUserRepository.Object);

    //Act
    var result = authentication.Login("user", "password");

    //Assert
    mockUserRepository.Verify(x => x.UpdateLastLoginDate("user"), Times.Never);
}


We verify that if incorrect data is provided, the UpdateLastLoginDate method will not be called.


Unit Testing School


If you would like to learn how to write unit tests in C#/.NET applications, consider joining the Unit Testing School - [here].


SUMMARY


Mocking objects in the world of unit testing is a very important topic. We often need to replace external dependencies with some artificial behavior. In today's article, I demonstrated with simple examples how this should be done in .NET, using Moq. I use the Moq framework, but if you want, you can of course use another framework. I recommend testing it and starting using the one that suits you best. They all have the same goals, i.e. mocking objects, but they differ mainly in syntax. If you have any questions about mocking or are missing more information or examples, please let me know in a comment. In the next article I will show you how to write integration tests in .NET. We will test the code that will add data to the database using the Entity Framework.

Author of the article:
Kazimierz Szpin

KAZIMIERZ SZPIN
Software Developer C#/.NET, Freelancer. Specializes in ASP.NET Core, ASP.NET MVC, ASP.NET Web API, Blazor, WPF and Windows Forms.
Author of the blog CodeWithKazik.com

Previous article - We Test Database Operations - Introduction to Integration Testing in .NET
Next article - What is Inheritance in Object Oriented Programming?
Comments (1)
Kazimierz Szpin
KAZIMIERZ SZPIN, czwartek, 11 lipca 2024 11:03
What do you think about this article? I'm curious about your opinion.
Dodaj komentarz

Search engine

© Copyright 2024 CodeWithKazik.com. All rights reserved. Privacy policy.
Design by Code With Kazik