DEV Community

Cover image for What is code pollution and how to fix it - A dependency injection lesson
Rogelio Gámez
Rogelio Gámez

Posted on

What is code pollution and how to fix it - A dependency injection lesson

What is code pollution?

Code pollution is an antipattern that takes place when you introduce additional code to your production code base to enable unit testing. [1]

This antipattern appears in two forms:

  1. As a new method only called from the unit tests.
  2. As a flag that changes the behavior of a class to indicate that it is called from a unit test.

Remember that code is a liability, not an asset. Adding code to production for the sole purpose of unit testing decreases the project's maintainability.

Examples of code pollution

Case 1: Adding a method that is only called from the unit tests.

public class MyClass
{
    private readonly IConfiguration _configuration;

    public MyClass(IConfiguration configuration) 
    {
        _configuration = configuration;
    }

    public string Run() 
    {
        if (GetConfigurationValue() == "FooBar")
        {
            return "Do cool stuff";
        }
        return string.Empty;
    }

    // Only used for testing
    internal string GetConfigurationValue()
    {
        return _configuration["ConfigurationValue"];
    }
}

[TestClass]
public class MyTest()
{
    [TestMethod]
    public void TestConfigurationValue()
    {
        string configurationValue = "FooBar";

        IConfiguration configuration = new ConfigurationBuilder()
            .AddInMemoryCollection(
                new Dictionary<string, string>() { "ConfigurationValue", configurationValue })
            .Build();

        MyClass myClass = new MyClass(configuration);

        Assert.AreEqual(configurationValue, myClass.GetConfigurationValue());
    }
}
Enter fullscreen mode Exit fullscreen mode

Case 2: Injecting a flag to change the class or method behavior.

public class MyClass
{
    private readonly IConfiguration _configuration;
    private bool _isTestEnvironment;

    public MyClass() {}

    internal MyClass(IConfiguration configuration, bool isTestEnvironment)
    {
        _configuration = configuration;
        _isTestEnvironment = isTestEnvironment;
    }

    public void Run(string createValue, string executeValue)
    {
        Repository myRepository;

        if (_isTestEnvironment)
        {
            // Stuff for test environment
            myRepository = CreateTestRepository(createValue);
        }
        else
        {
            // Stuff for normal environment
            myRepository = CreateRepository(_configuration, createValue);
        }

        myRepository.DoStuff(executeValue);
    }
}
Enter fullscreen mode Exit fullscreen mode

How to fix code pollution

Case 1. Fixing method or property pollution.

If you are exposing private properties or methods, you are exposing implementation details. In that case, you should change your unit test to actually test the system's behavior.

[TestClass]
public class MyTest()
{
    [TestMethod]
    public void TestConfigurationValue()
    {
        string configurationValue = "FooBar";

        IConfiguration configuration = new ConfigurationBuilder()
            .AddInMemoryCollection(
                new Dictionary<string, string>() { "ConfigurationValue", configurationValue })
            .Build();

        MyClass myClass = new MyClass(configuration);

        Assert.AreEqual("Do cool stuff", myClass.Run());
    }
}
Enter fullscreen mode Exit fullscreen mode

Case 2. Fixing injection pollution.

If you need to inject a parameter or configuration to test the system, you are not applying dependency injection properly.

In our example, we use a bool flag to indicate our program to create a test Repository instead of the normal one to avoid using production dependencies IConfiguration.

Instead, we should inject Repository as a method dependency, or inject a RepositoryBuilder as a constructor dependency.

// Method injection
public class MyClass
{
    public MyClass() {}

    public void Run(Repository repository, string executeValue)
    {
        repository.DoStuff(executeValue);
    }
}

// Constructor injection
public class MyClass
{
    private readonly RepositoryBuilder _repositoryBuilder;

    public MyClass(RepositoryBuilder repositoryBuilder) 
    {
        _repositoryBuilder = repositoryBuilder;
    }

    public void Run(string executeValue)
    {
        _respositoryBuilder.Build().DoStuff(executeValue);
    }
}
Enter fullscreen mode Exit fullscreen mode

This way, we can inject the testing repo from our unit test, and the production repo in the production code base.

References

  1. Khorikov, V. (2020). Unit testing principles, practices, and patterns. Manning Publications Company.

Top comments (0)