Unit tests code smells – Test code duplication

I would bet that everyone has duplicated the test code at least once in their career. The problem is even more common when we decide to write tests at the very end and therefore spend less time on them. On top of that, there is a common belief that tests are not production code and we don’t have to care about them in the same way as production code, but it’s a naive approach. Badly written tests can cause that a small change in the production code will require many adjustments in the tests, and this situation will repeat itself many times.

Test code duplication most often occurs when we:

  • Copy Arrange/Act parts between tests,
  • Solve the same problem many times,
  • Don’t spend enough time on refactoring already written tests.

Duplicate code is very easy to detect when reading tests from the test suite. We often come across duplicate sections that differ slightly or not at all. It’s a bit harder to spot duplicate code between different test suites. You can use static code analysis tools such as SonarQube or ReSharper to detect such dependencies. I will show you a simple example of code that is duplicated between tests:

public class TestSuite
{
    [Fact]
    public void Should_ReturnAllModels()
    {
        // Arrange
        var modelProvider = new ModelProvider(new List<Model>
        {
            new Model()
            {
                Id = "Id-6551A6A4-7D17-4EAA-AE87-AE36096B8C75",
                Name = "Name-7E6EFE56-EDC3-41A3-B9EB-1C46273B2467",
                Value = "Value-7E6EFE56-EDC3-41A3-B9EB-1C46273B2467"
            },
            new Model()
            {
                Id = "Id-5042CDC4-4D13-43E9-953E-FB7923168366",
                Name = "Name-0232030A-704F-407C-B85E-63FE3F14F37D",
                Value = "Value-FF667E27-93B2-4CB4-A773-4AA0FF852A38"
            }
        });
        var sut = new ModelService(modelProvider);

        // Act
        var result = sut.GetAll();

        // Assert
        result.Should().NotBeNullOrEmpty();
        result.Should().HaveCount(2);

        result[0].Id.Should().Be("Id-6551A6A4-7D17-4EAA-AE87-AE36096B8C75");
        result[0].Name.Should().Be("Name-7E6EFE56-EDC3-41A3-B9EB-1C46273B2467");
        result[0].Value.Should().Be("Value-7E6EFE56-EDC3-41A3-B9EB-1C46273B2467");

        result[1].Id.Should().Be("Id-5042CDC4-4D13-43E9-953E-FB7923168366");
        result[1].Name.Should().Be("Name-0232030A-704F-407C-B85E-63FE3F14F37D");
        result[1].Value.Should().Be("Value-FF667E27-93B2-4CB4-A773-4AA0FF852A38");
    }

    [Fact]
    public void Should_ReturnModel_WhenCorrectIdProvided()
    {
        // Arrange
        var modelProvider = new ModelProvider(new List<Model>
        {
            new Model()
            {
                Id = "Id-6551A6A4-7D17-4EAA-AE87-AE36096B8C75",
                Name = "Name-7E6EFE56-EDC3-41A3-B9EB-1C46273B2467",
                Value = "Value-7E6EFE56-EDC3-41A3-B9EB-1C46273B2467"
            },
            new Model()
            {
                Id = "Id-5042CDC4-4D13-43E9-953E-FB7923168366",
                Name = "Name-0232030A-704F-407C-B85E-63FE3F14F37D",
                Value = "Value-FF667E27-93B2-4CB4-A773-4AA0FF852A38"
            }
        });
        var sut = new ModelService(modelProvider);

        // Act
        var result = sut.GetById("Id-6551A6A4-7D17-4EAA-AE87-AE36096B8C75");

        // Assert
        result.Should().NotBeNull();

        result.Id.Should().Be("Id-6551A6A4-7D17-4EAA-AE87-AE36096B8C75");
        result.Name.Should().Be("Name-7E6EFE56-EDC3-41A3-B9EB-1C46273B2467");
        result.Value.Should().Be("Value-7E6EFE56-EDC3-41A3-B9EB-1C46273B2467");
    }
}

ModelService is a service that we will test. It allows to fetch all data at once and by id. In both tests it is easy to notice that the Arrange and Assert sections have common parts that can be reduced. Let’s start with the Arrange section. One of the dependencies is created the same way each time. Here we have a couple of options for improving this, we can create a method that will take care of building the dependency or move the dependency creation to the constructor. Due to the simplicity of this example, I will follow second approach.

public class TestSuite
{
    ...
    private readonly ModelProvider modelProvider;

    public TestSuite()
    {
        modelProvider = new ModelProvider(new List<Model>
        {
            new Model()
            {
                Id = "Id-6551A6A4-7D17-4EAA-AE87-AE36096B8C75",
                Name = "Name-7E6EFE56-EDC3-41A3-B9EB-1C46273B2467",
                Value = "Value-7E6EFE56-EDC3-41A3-B9EB-1C46273B2467"
            },
            new Model()
            {
                Id = "Id-5042CDC4-4D13-43E9-953E-FB7923168366",
                Name = "Name-0232030A-704F-407C-B85E-63FE3F14F37D",
                Value = "Value-FF667E27-93B2-4CB4-A773-4AA0FF852A38"
            }
        });
    }
    ...
}

There is also an Assert section. In this section you can see assertions on the model several times, this type of problem could be quickly solved by using the BeEquivalentTo method, but in this case I will take a different path and extract assertion to method.

private void AssertModel(Model model, string expectedId, string expectedName, string expectedValue)
{
    model.Id.Should().Be(expectedId);
    model.Name.Should().Be(expectedName);
    model.Value.Should().Be(expectedValue);
}

We improved the readability and maintainability of tests by reducing duplicate code. Depending on the case, we could additionally move the methods to separate Fixtures and reuse them in other test suites. It’s also common to extend Assert by appending method with more complex validations.

Reducing duplication of test code is very important in unit testing. As the test code grows, its clarity decreases and we need to spend more time analyzing and adjusting it. Neglecting this topic results in a situation where our tests cannot give us what we expect from them.

This is part of the code smell in unit tests series. If the described problem happened to you, please share your story in the comments below. I also encourage you to follow this series, another code smell is coming soon!

2 thoughts on “Unit tests code smells – Test code duplication”

  1. Pingback: dotnetomaniak.pl
  2. Get 450+ Extra Daily Visitors and 3X Your Profits

    “How can I get more traffic to my site?”
    People ask me this question all the time.
    Truth be told, ranking on Google is getting *harder*,
    because everyone and their grandma is targeting
    the same keywords.
    My friend George and his team just released a new
    SEO WordPress plugin that fixes this problem.
    It ranks your site higher in Google, without you
    creating more content or building backlinks.

    Check it out here ==> https://bit.ly/39swbCx

    Reply

Leave a Comment

Share via
Copy link
Powered by Social Snap