Blog for Junior Developers C#/.NET

Tuesday, July 09, 2024

From my previous articles, you already know that automatic tests can be divided into, among others, unit tests and integration tests. I have already introduced you to unit tests in recent articles (if you haven't read them yet, be sure to do so). Now it's time for integration tests. In unit testing, we test single units, without external dependencies. It may turn out that we have a lot of unit tests that are green, but in fact there may be bugs in our application. We may have errors in, for example, the external dependencies mentioned earlier. To check this, we also need to write integration tests that test several units connected to each other and cooperation with external dependencies.

we-test-database-operations-introduction-to-integration-testing-in-dotnet.jpg

Why separate unit tests from integration tests?


I found several posts on the Internet where programmers write that they see no point in separating unit tests from integration tests. They write that a test is just a test and there is no point in separating it and introducing strange nomenclature. They then usually call all tests unit tests. I do not agree with this and I always introduce such a division in my projects. In my opinion, unit tests need to be separated from integration tests. First of all, this division introduces order to my project. After separating two types of tests into separate projects, I know that for integration tests I have, for example, a separate configuration that must be completed correctly, there is also a separate database, and the entire environment for such tests must be prepared in advance. In unit testing, I know that the test must always return the same result to me and I don't have to worry about other factors that might affect the test. Additionally, integration tests take much longer to complete than unit tests and they are not run as often. In turn, unit tests, because they are very fast, can be performed even with every build, which means we have quick feedback if something doesn't work or we have some regression in our code. If the tests are not separated from each other, then all of them are run rarely, over time less and less frequently, because we don't want to wait that long for the result, and in the case of integration tests, this will always take time. Can you imagine running tests that take several minutes after each build? Not really.


Preparation for integration tests


If we want to write integration tests, we need to prepare an appropriate environment. Let's assume that we want to test some methods that contain queries or database commands. In such a case, it is best to prepare a separate database for integration tests. In the configuration file for the project with integration tests, define a separate connection string for the database prepared for integration tests. There are different approaches to testing cooperation with a database, including testing on a database created in memory, but I will present a different approach. We will execute all the logic that works with the database in transactions and after each test we will rollback the transaction. Due to the fact that we often rely on external dependencies, integration tests take longer to execute than unit tests.


What does a sample integration test written in C# look like?


Let's assume we have a UserRepository class which, not to complicate things too much, just adds new users using the Entity Framework.

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class UserRepository
{
    public void Add(User user)
    {
        using (var context = new ApplicationDbContext())
        {
            context.Users.Add(user);
            context.SaveChanges();
        }
    }
}


As you can see, this class has external dependencies - it works with the database.

#1 Database for integration tests.
To conduct tests in such a case, it is best to prepare a database for integration tests.

#2 New design for integration tests.
To avoid mixing integration tests with other tests. It is best to create a separate project with only integration tests. Thanks to this division, unit tests and integration tests can be run separately. You can name the new project Project.IntegrationTests.

#3 We install the required frameworks.
In my case it will be EntityFramework, NUnit and NUnit3TestAdapter.

#4 Configuration.
In the app.config file I complete the connection string for my test database.

#5 Test isolation.
I want the data I added to disappear from the database after each test. To do this, all my commands must be executed in a transaction. We can do this by adding our own attribute, which will then be added to each test after which we want to clean the data. We add a class, for example, called Isolated. By marking our testing class with the [Isolated] attribute, a new transaction will be created before each test, and a rollback of this transaction will be triggered after each test.

public class Isolated : Attribute, ITestAction
{
    private TransactionScope _transactionScope;

    public ActionTargets Targets
    {
        get { return ActionTargets.Test; }
    }

    public void AfterTest(ITest test)
    {
        _transactionScope.Dispose();
    }

    public void BeforeTest(ITest test)
    {
        _transactionScope = new TransactionScope();
    }
}


#6 New class for integration tests of the UserRepository class.
Sticking to convention, we add a new class to the test project called UserRepositoryTests. Here will be all the methods testing the UserRepository class.

#7 We are writing an integration test.

public class UserRepositoryTests
{
    [Test, Isolated]
    public void Add_PassValidUser_ShouldAddUserToDatabase()
    {
        var context = new ApplicationDbContext();
        var user = new User { Id = 1, Name = "name" };
        var userRepository = new UserRepository();

        userRepository.Add(user);

        var usersCount = context.Users.Count(x => x.Id == user.Id && x.Name == user.Name);
        Assert.That(usersCount, Is.EqualTo(1));
    }
}


We must remember to mark the method with the Test and Isolated attributes. We have added a method that, after passing the user, should add it to the database. First, there is the initialization of the objects that we use in the test. We add a user via the Add method of the UserRepository class. Finally, we check whether there is only one user with such data in the database. The test passes, additionally, if we run the test many times, it will always pass because after each test, a transaction rollback is performed on the database.


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:


I hope that I managed to introduce you to the world of integration testing with this simple example. You see the difference between unit tests and integration tests. This example was quite simple, I didn't want to bother you with complicated examples at the beginning. If you are interested in the topic of integration tests on the blog, I will try to present more complicated examples in subsequent articles.

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 - Test Driven Development: Benefits of Using TDD on an Example in .NET
Next article - How to Get Rid of External Dependencies in Unit Tests? Introduction to Mocking Data in C#
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