What is code pollution and how to fix it - A dependency injection lesson
Rogelio Gámez
Posted on June 22, 2024
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:
- As a new method only called from the unit tests.
- 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());
}
}
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);
}
}
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());
}
}
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);
}
}
This way, we can inject the testing repo from our unit test, and the production repo in the production code base.
References
- Khorikov, V. (2020). Unit testing principles, practices, and patterns. Manning Publications Company.
Posted on June 22, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.