Testing for Developers- Isolation Frameworks Fundamentals

Testing for Developers- Isolation Frameworks Fundamentals

In the series we will define the basic terms that every developer needs to know about testing. The purpose is to give all team members a shared understanding of the fundamental terminology of the quality assurance and all related processes. Later this will improve the communication and reviews quality. It will further increase the testing capabilities of each member. In this part, we will talk about the more advanced concepts and terminology in unit testing such as mocking and stubbing. In the previous part of the series- we talked about the basic concepts of unit testing.

As part of the professional services we provide at BELLATRIXwe consult companies and help them to improve their QA process and set up an automated testing infrastructure. After the initial review process and giving improvement recommendations for some companies we need to hire new talents that can help the company to scale-up the solutions we provided. This was part of training we did for a company we consulted so that we educate all of their developers.

Introduction

Flying people into space presents interesting challenges to engineers and astronauts, one of the more difficult being how to make sure the astronaut is ready to go into space and operate all the machinery during orbit. A full integration test for the space shuttle would have required being in space, and that’s obviously not a safe way to test astronauts. That’s why NASA built full simulators that mimicked the surroundings of a space shuttle’s control deck, which removed the external dependency of having to be in outer space.

An external dependency is an object in your system that your code under test interacts with and over which you have no control. (Common examples are file systems, threads, memory, time, and so on.)

A stub is a controllable replacement for an existing dependency (or collaborator) in the system. By using a stub, you can test your code without dealing with the dependency directly.

Break Dependency

You can’t test something? Add a layer that wraps up the calls to that something, and then mimic that layer in your tests.

Find the interface that the start of the unit of work under test works against. (In this case, “interface” isn’t used in the pure object-oriented sense; it refers to the defined method or class being collaborated with.)

If the interface is directly connected to your unit of work under test (as in this case—you’re calling directly into the file-system), make the code testable by adding a level of indirection hiding the interface.

Replace the underlying implementation of that interactive interface with something that you have control over. In this case, you’ll replace the instance of the class that your method calls (FileExtensionManager) with a stub class that you can control (StubExtensionManager), giving your test code control over external dependencies.

break dependency

public List<string> GeneratePiesNamesByCurrentYear()
{
    var brandedPiesNames = new List<string>();
    foreach (var pie in _originalPiesNames)
    {
        brandedPiesNames.Add($"{DateTime.Now.Year} {pie}");
    }
    return brandedPiesNames;
}

DateTime.Now is our real dependency.

public interface IDateTimeFacade
{
    DateTime GetCurrentDateTime();
}

Now we replace the dependency. 

public List<string> GeneratePiesNamesByCurrentYear()
{
    var brandedPiesNames = new List<string>();
    foreach (var pie in _originalPiesNames)
    {
        brandedPiesNames.Add($"{_dateTimeFacade.GetCurrentDateTime().Year} {pie}");
    }
    return brandedPiesNames;
}

Refactoring is the act of changing code without changing the code’s functionality.

That is, it does exactly the same job as it did before. No more and no less. It just looks different. A refactoring example might be renaming a method and breaking a long method into several smaller methods.

Seams are places in your code where you can plug in different functionality.

Seams are places in your code where you can plug in different functionality, such as stub classes, adding a constructor parameter, adding a public settable property, making a method virtual so it can be overridden, or externalizing a delegate as a parameter or property so that it can be set from outside a class. Seams are what you get by implementing the Open-Closed Principle, where a class’s functionality is open for extenuation, but its source code is closed for direct modification.

There are two types of dependency-breaking refactorings, and one depends on the other. I call them Type A and Type B refactorings.

Type A Refactoring- abstracting concrete objects into interfaces or delegates.

Type B Refactoring- refactoring to allow injection of fake implementations of those delegates or interfaces.

public class AlwaysValidFakeExtensionManager : IExtensionManager
{
    public bool IsValid(string fileName)
    {
        return true;
    }
}

It’s not StubExtensionManager or MockExtensionManager. It’s FakeExtensionManager. A fake denotes an object that looks like another object but can be used as a mock or a stub.

This fake extension manager will always return true, so name the class AlwaysValidFakeExtensionManager, so that the reader of your future test will understand what will be the behavior of the fake object, without needing to read its source code.

classDiagram
    IExtensionManager <|.. AlwaysValidFakeExtensionManager
    LogAnalyzer --> IExtensionManager
    class IExtensionManager {
        <<interface>>
        +IsValid(string fileName) bool
    }
    class AlwaysValidFakeExtensionManager {
        +IsValid(string fileName) bool
    }
    class LogAnalyzer {
        -IExtensionManager manager
        +IsValidLogFileName(string fileName) bool
    }

Injection Types

Here are some of the most notable ways:

Receive an interface at the constructor level and save it in a field for later use.

Constructor Level

private readonly ILogger<TestCaseRunsController> _logger;
private readonly MeissaRepository _meissaRepository;
public TestCaseRunsController(ILogger<TestCaseRunsController> logger, MeissaRepository repository)
{
    _logger = logger;
    _meissaRepository = repository;
}

Property Level

public TestCaseRunsController(ILogger<TestCaseRunsController> logger, MeissaRepository repository)
{
    _logger = logger;
}
public MeissaRepository MeissaRepository { get; set; }

Method Parameter Level

public async Task SetAllActiveAgentsToVerifyTheirStatusAsync(IServiceClient<TestAgentDto> testAgentRepository, string tag)
{
    var testAgents = await GetAllActiveTestAgentsByTagAsync(tag);
    if (testAgents.Count > 0)
    {
        await UpdateAgentsStatusAsync(testAgents, TestAgentStatus.RequestActiveConfirmation);
    }
}

Receive an interface as a property get or set and save it in a field for later use.

Local Factory Method

public async Task SetAllActiveAgentsToVerifyTheirStatusAsync(string tag)
{
    var repo = CreateTestAgentRepo();
    var testAgents = await repo.GetAllActiveTestAgentsByTagAsync(tag);
    if (testAgents.Count > 0)
    {
        await UpdateAgentsStatusAsync(testAgents, TestAgentStatus.RequestActiveConfirmation);
    }
}
private IServiceClient<TestAgentDto> CreateTestAgentRepo() => new TestAgentServiceClient();

Factory Class

public class TestAgentRepoFactory : ITestAgentRepoFactory
{
    public IServiceClient<TestAgentDto> CreateTestAgentRepo()
    {
        return new TestAgentServiceClient();
    }
}

The constructor then sets a local field of the interface type in the class for later use by your method or any other. The fake extension manager is located in the same file as the test code because currently the fake is used only from within this test class.

classDiagram
    IExtensionManager <|.. FakeExtensionManager
    LogAnalyzer --> IExtensionManager
    class IExtensionManager {
        <<interface>>
        +IsValid(string fileName) bool
    }
    class FakeExtensionManager {
        +WillBeValid bool
        +IsValid(string fileName) bool
    }
    class LogAnalyzer {
        -IExtensionManager manager
        +LogAnalyzer(IExtensionManager mgr)
        +IsValidLogFileName(string fileName) bool
    }
internal class FakeExtensionManager : IExtensionManager
{
    public bool WillBeValid = false;
    public bool IsValid(string fileName)
    {
        return WillBeValid;
    }
}
public class LogAnalyzer
{
    private IExtensionManager manager;
    public LogAnalyzer(IExtensionManager mgr)
    {
        manager = mgr;
    }
    public bool IsValidLogFileName(string fileName)
    {
        return manager.IsValid(fileName);
    }
}
public interface IExtensionManager
{
    bool IsValid(string fileName);
}

public void IsValidFileName_NameSupportedExtension_ReturnsTrue()
{
    FakeExtensionManager myFakeManager = new FakeExtensionManager();
    myFakeManager.WillBeValid = true;
    LogAnalyzer log = new LogAnalyzer(myFakeManager);
    bool result = log.IsValidLogFileName("short.ext");
    Assert.True(result);
}

Constructor Injection Problems

Problems can arise from using constructors to inject implementations. If your code under test requires more than one stub to work correctly without dependencies, adding more and more constructors (or more and more constructor parameters) becomes a hassle, and it can even make the code less readable and less maintainable.

many injection dependencies

Solution: One solution is to create a special class that contains all the values needed to initialize a class and to have only one parameter to the method: that class type. That way, you only pass around one object with all the relevant dependencies. (This is also known as a parameter object refactoring.)

Property Injection

classDiagram
    IExtensionManager <|.. FileExtensionManager
    LogAnalyzer --> IExtensionManager
    class IExtensionManager {
        <<interface>>
        +IsValid(string fileName) bool
    }
    class FileExtensionManager {
        +IsValid(string fileName) bool
    }
    class LogAnalyzer {
        +ExtensionManager IExtensionManager
        +IsValidLogFileName(string fileName) bool
    }

Using properties to inject dependencies. This is much simpler than using a constructor because each test can set the properties that it needs to get the test underway.

public class LogAnalyzer
{
    private IExtensionManager manager;
    public LogAnalyzer()
    {
        manager = new FileExtensionManager();
    }
    public IExtensionManager ExtensionManager
    {
        get { return manager; }
        set { manager = value; }
    }
    public bool IsValidLogFileName(string fileName)
    {
        return manager.IsValid(fileName);
    }
}

public void IsValidFileName_SupportedExtension_ReturnsTrue()
{
    //set up the stub to use, make sure it returns true
    // ...
    //create analyzer and inject stub
    LogAnalyzer log = new LogAnalyzer();
    log.ExtensionManager = someFakeManagerCreatedEarlier;
    //Assert logic assuming extension is supported
    //...
}

When to Use?

Use this technique when you want to signify that a dependency of the class under test is optional or if the dependency has a default instance created that doesn’t create any problems during the test.

Factory Class

A test configures the factory class to return a stub object. The class under test uses the factory class to get that instance, which in production code would return an object that isn’t a stub.

classDiagram
    IExtensionManager <|.. FileExtensionManager
    IExtensionManager <|.. StubExtensionManager
    ExtensionManagerFactory --> IExtensionManager
    LogAnalyzer --> ExtensionManagerFactory
    class IExtensionManager {
        <<interface>>
        +IsValid(string fileName) bool
    }
    class ExtensionManagerFactory {
        +Create() IExtensionManager
    }
    class LogAnalyzer {
        +IsValidLogFileName(string fileName) bool
    }

The only thing you need to make sure of is that once you use these patterns, you add a seam to the factories you make so that they can return your stubs instead of the default implementations.

Extract and Override

In this scenario, you use a local virtual method in the class under test as a factory to get the instance of the extension manager. Because the method is marked as virtual, it can be overridden in a derived class, which creates your seam.

extract override

You inherit from the class under test so you can override its virtual factory method and return whatever you want, as long as it implements IExtensionManager. Then you perform your tests against the newly derived class.

public class DateTimeProvider : IDateTimeProvider
{
    public DateTime GetCurrentTime() => DateTime.Now;
}

But why stop there? What if you’re unable or unwilling to add a new interface every time you need control over some behavior in your code under test? In those cases, Extract and Override can help simplify things, because it doesn’t require writing and introducing new interfaces—just deriving from the class under test and overriding some behavior in the class.

Internal Instead of Public

public class LogAnalyzer
{
    //...
    internal LogAnalyzer(IExtensionManager extentionMgr)
    {
        manager = extentionMgr;
    }
    //...
}
using System.Runtime.CompilerServices;
[assembly:
InternalsVisibleTo("Meissa.Infrastructure")]

If the build flag is not present during the build, the callers to the annotated method won’t be included in the build. For example, this method will have all the callers to it removed during a release build, but the method itself will stay on.

[Conditional("DEBUG")]
public string GetRunningAssemblyPath()
{
    string codeBase = Assembly.GetExecutingAssembly().CodeBase;
    var uri = new UriBuilder(codeBase);
    string path = Uri.UnescapeDataString(uri.Path);
    return Path.GetDirectoryName(path);
}

It’s important to note that using conditional compilation constructs in your production code can reduce its readability and increase its “spaghetti-ness.” Beware!

Putting your methods or special test-only constructors between #if and #endif constructs will make sure they compile only when that build flag is set, as shown in the next listing.

#if DEBUG
public LogAnalyzer (IExtensionManager extensionMgr)
{
manager = extensionMgr;
}
#endif

This method is commonly used, but it can lead to code that looks messy.

Types of Testing

Value-based testing checks the value returned from a function. 

State-based testing is about checking for noticeable behavior changes in the system under test, after changing its state.

Interaction testing is testing how an object sends messages (calls methods) to other objects. You use interaction testing when calling another object is the end result of a specific unit of work.

You can also think of interaction testing as being action-driven testing. Action-driven testing means that you test a particular action an object takes (such as sending a message to another object). Always choose to use interaction testing only as the last option. But sometimes, as is the case of a third-party call to a logger, interactions between objects are the end result. That’s when you need to test the interaction itself.

Stubs vs Mocks

A mock object is a fake object in the system that decides whether the unit test has passed or failed. It does so by verifying whether the object under test called the fake object as expected.

But it’s a smarter breed of stub—a stub that records the calls made to it, and you use it to define if your test passed or not. That’s partly what a mock object is. There’s usually no more than one mock per test.

Fake- generic term that can be used to describe either a stub or a mock object.

A fake is a generic term that can be used to describe either a stub or a mock object (handwritten or otherwise), because they both look like the real object. Whether a fake is a stub or a mock depends on how it’s used in the current test. If it’s used to check an interaction (asserted against), it’s a mock object. Otherwise, it’s a stub.

When using a stub the assert is performed on the class under test. The stub aids in making sure the test runs smoothly.

stub-diagram

The class under test communicates with the mock object, and all communication is recorded in the mock. The test uses the mock object to verify that the test passes.

This handwritten class implements an interface, as a stub does, but it saves some state for later, so that your test can then assert and verify that your mock was called correctly.

mock diagram

Spy- according to xUnit Test Patterns: Refactoring Test Code by Gerard Meszaros, this would be called a Test Spy.

Multiple Stubs


public async Task InsertCurrentTestAgent_When_TestAgentForCurrentMachineIsNotExistingInDatabase()
{
    // Arrange
    var testAgents = TestAgentFactory.CreateWithoutCurrentMachineName(TestAgentStatus.Inactive);
    var insertedTestAgent = default(Task<TestAgentDto>);
    _testAgentRepositoryMock.Setup(x => x.GetAllAsync()).Returns(Task.FromResult(testAgents));
    _testAgentRepositoryMock.Setup(x => x.CreateAsync(It.IsAny<TestAgentDto>())).
    Returns((Task<TestAgentDto> a) => insertedTestAgent = a);
    // Act
    await _testAgentStateSwitcher.SetTestAgentAsActiveAsync(testAgents.First().AgentTag);
    // Assert
    // ...
}

As you’ll see, it’s perfectly OK to have multiple stubs in a single test, but more than a single mock can mean trouble, because you’re testing more than one thing.

Multiple Asserts

Assert.That(insertedTestAgent.Result.TestAgentId, Is.Not.Null);
Assert.That(insertedTestAgent.Result.AgentTag, Is.EqualTo(testAgents.First().AgentTag));
Assert.That(insertedTestAgent.Result.MachineName, Is.EqualTo(Environment.MachineName));
Assert.That(insertedTestAgent.Status, Is.EqualTo(TestAgentStatus.Active));

Having several asserts can sometimes be a problem, because the first time an assert fails in your test, it actually throws a special type of exception that is caught by the test runner. That also means no other lines below the line that just failed will be executed. In this current case, it’s OK, because if one assert fails, you don’t care about the others because they’re all related to the same object, and they’re part of the same “feature.” If you cared about the other asserts being run even if the first one failed, it would be a good indication to you to break this test into multiple tests.

Multiple Mocks

_testRunRepositoryMock.Verify(x => x.UpdateAsync(It.IsAny<int>(),
It.Is<TestRunDto>(i => i.TestRunId == _testRunId && i.Status == status)), Times.Once);
_testRunRepositoryMock.Verify(x => x.UpdateAsync(It.IsAny<int>(),
It.IsAny<TestRunDto>()), Times.Once);

Having more than one mock per test usually means you’re testing more than one thing, and this can lead to complicated or brittle tests. Overspecification is the act of specifying too many things that should happen that your test shouldn’t care about; for example, that stubs were called. These extra specifications can make your test fail for all the wrong reasons.

Manual Mocks

There are several issues that crop up when using manual mocks and stubs:

It takes time to write the mocks and stubs.

It’s difficult to write stubs and mocks for classes and interfaces that have many methods, properties, and events.

To save state for multiple calls of a mock method, you need to write a lot of boilerplate code within the handwritten fakes.

Isolation Framework

An isolation framework is a set of programmable APIs that makes creating fake objects much simpler, faster, and shorter than hand-coding them.

A dynamic fake object is any stub or mock that’s created at runtime without needing to use a handwritten (hardcoded) implementation of that object.

Moq Isolation Framework

Install-Package Moq

You’ll use to generate fakes at runtime. Because Moq is a constrained framework, it works best with interfaces. For real classes, it will only work with nonsealed classes, and for those, it will only be able to fake virtual methods.

Moq Initialize

private Mock<IServiceClient<TestAgentDto>> _testAgentRepositoryMock;
private ITestAgentsService _testAgentsService;

public void TestInit()
{
    _testAgentRepositoryMock = new Mock<IServiceClient<TestAgentDto>>();
    _testAgentsService = new TestAgentsService(_testAgentRepositoryMock.Object);
}

Moq Stubs

_directoryProvider.Setup(x => x.DoesDirectoryExists(It.IsAny<string>())).Returns(true);
_dateTimeProvider.Setup(x => x.GetCurrentTime()).Returns(DateTime.Now);

Moq Async Stubs

var logs = LogFactory.CreateEmpty();
_logRepositoryMock.Setup(x => x.GetAllAsync()).Returns(Task.FromResult(logs));

Moq Stub Lambdas

_pathProvider.Setup(x => x.Combine(It.IsAny<string>(), It.IsAny<string>())).
Returns((string path1, string filePath) => Path.Combine(path1, filePath));
_fileProvider.Setup(x => x.WriteAllText(It.IsAny<string>(), It.IsAny<string>())).
Callback((string filePath, string content) => File.WriteAllText(filePath, content));

Moq Throw Exceptions

Mock<IFileConnection> fileConnection = new Mock<IFileConnection>();
fileConnection.Setup(item => item.Get(It.IsAny<string>, It.IsAny<string>))
.Throws(new IOException());

Raising Events from Mock Objects

var mockHeater = new Mock<IHeater>();
var mockThermostat = new Mock<IThermostat>();
mockThermostat.Setup(m => m.StartAsyncSelfCheck()).Raises(
m => m.HealthCheckComplete += null, new ThermoEventArgs { OK = false });
var controller = new Services.HeatingController(mockHeater.Object, mockThermostat.Object);
// Act
controller.PerformHealthChecks();
// Assert
mockHeater.Verify(m => m.SwitchOff());

Delegate Fired Manually


public void EventFiringManual()
{
    bool loadFired = false;
    SomeView view = new SomeView();
    view.Load += delegate
    {
        loadFired = true;
    };
    view.DoSomethingThatEventuallyFiresThisEvent();
    Assert.IsTrue(loadFired);
}

Raising Events in Response to Expectations

// Arrange
var mockHeater = new Mock<IHeater>();
var mockThermostat = new Mock<IThermostat>();
mockThermostat.Setup(m => m.StartAsyncSelfCheck()).Raises(
m => m.HealthCheckComplete += null, new ThermoEventArgs { OK = false });
var controller = new Services.HeatingController(mockHeater.Object, mockThermostat.Object);
// Act
controller.PerformHealthChecks();
// Assert
mockHeater.Verify(m => m.SwitchOff());
// Arrange
var mockHeater = new Mock<IHeater>();
var mockThermostat = new Mock<IThermostat>();
mockThermostat.Setup(m => m.StartAsyncSelfCheck()).Raises(
m => m.HealthCheckComplete += null, new ThermoEventArgs { OK = true });
var controller = new Services.HeatingController(mockHeater.Object, mockThermostat.Object);
// Act
controller.PerformHealthChecks();
// Assert
mockHeater.Verify(m => m.SwitchOff(), Times.Never());

References

The Art of Unit Testing, Second Edition - Manning

Refactoring: Improving the Design of Existing Code 2nd Edition

Working Effectively with Legacy Code (Michael C. Feathers)

Related Articles

High-Quality Automated Tests

High-Quality Automated Tests- Top 10 EditorConfig Coding Styles Part 1

This is the second article from the new series- High-Quality Automated Tests. In the previous publication, I showed you how to apply coding standards and styles

High-Quality Automated Tests- Top 10 EditorConfig Coding Styles Part 1

High-Quality Automated Tests

High-Quality Code- Naming Methods

In the last articles from the High-Quality Automated Tests we talked about coding styles and various tools that can help you to force them automatically. In thi

High-Quality Code- Naming Methods

High-Quality Automated Tests

High-Quality Automated Tests- Top 10 EditorConfig Coding Styles Part 2

This is the third article from the new series- High-Quality Automated Tests. In the first publication, I showed you how to apply coding standards and styles in

High-Quality Automated Tests- Top 10 EditorConfig Coding Styles Part 2

High-Quality Automated Tests

High-Quality Code- Naming Classes, Interfaces, Enumerations

In the last articles from the High-Quality Automated Tests we talked about coding styles and various tools that can help you to force them automatically. In thi

High-Quality Code- Naming Classes, Interfaces, Enumerations

High-Quality Automated Tests

Testing for Developers- Unit Testing Fundamentals

In the series we will define the basic terms that every developer needs to know about testing. The purpose is to give all team members a shared understanding of

Testing for Developers- Unit Testing Fundamentals

High-Quality Automated Tests

High-Quality Automated Tests- Consistent Coding Styles Using EditorConfig

This is the first article from a new series of posts called- High-Quality Automated Tests. I firmly believe that all coded tests should be treated with respect

High-Quality Automated Tests- Consistent Coding Styles Using EditorConfig
Anton Angelov

About the author

Anton Angelov is Managing Director, Co-Founder, and Chief Test Automation Architect at Automate The Planet — a boutique consulting firm specializing in AI-augmented test automation strategy, implementation, and enablement. He is the creator of BELLATRIX, a cross-platform framework for web, mobile, desktop, and API testing, and the author of 8 bestselling books on test automation. A speaker at 60+ international conferences and researcher in AI-driven testing and LLM-based automation, he has been recognized as QA of the Decade and Webit Changemaker 2025.