This post I will show how to create a testing context which can be used in unit tests to automatically construct the testing target. My solution is based on this article from https://dotnetcoretutorials.com. I’ve customised it a bit to allow mock Target and automatically inject all the mocks during setup. The customisation will give you a mock Target which allows you to setup the returns of your public virtual methods. It also allows asserting if a method on a dependency is called without having to call GetMockFor<> in test arrangement.
I’m going to show the code and then show how it is used and the problems it solves.
[TestFixture]
public abstract class TestingContext<T> where T : class
{
private Fixture _fixture;
private Dictionary<Type, Mock> _injectedMocks;
private Dictionary<Type, object> _injectedConcreteClasses;
[SetUp]
public void BaseSetup()
{
_fixture = new Fixture();
_fixture.Customize(new AutoMoqCustomization());
_injectedMocks = new Dictionary<Type, Mock>();
_injectedConcreteClasses = new Dictionary<Type, object>();
SetUp();
// Inject the rest of constructor params that have not been injected during SetUp
var type = typeof(T);
var constructors = type.GetConstructors();
var method = typeof(TestingContext<T>).GetMethod("GetMockFor");
foreach (var contructor in constructors)
{
var parameters = contructor.GetParameters();
foreach (var parameter in parameters)
{
var parameterType = parameter.ParameterType;
if (!_injectedConcreteClasses.ContainsKey(parameterType) && (parameterType.IsInterface || parameterType.IsAbstract))
{
var genericMethod = method.MakeGenericMethod(parameterType);
genericMethod.Invoke(this, null);
}
}
}
}
public virtual void SetUp() { }
/// <summary>
/// Generates a mock for a class and injects it into the final fixture
/// </summary>
/// <typeparam name="TMockType"></typeparam>
/// <returns></returns>
public Mock<TMockType> GetMockFor<TMockType>() where TMockType : class
{
var mockType = typeof(TMockType);
var existingMock = _injectedMocks.FirstOrDefault(x => x.Key == mockType);
if (existingMock.Key == null)
{
var newMock = new Mock<TMockType>();
_injectedMocks.Add(mockType, newMock);
_fixture.Inject(newMock.Object);
return newMock;
}
return existingMock.Value as Mock<TMockType>;
}
/// <summary>
/// Injects a concrete class to be used when generating the fixture.
/// </summary>
/// <typeparam name="ClassType"></typeparam>
/// <returns></returns>
public void InjectMock<ClassType>(ClassType injectedClass) where ClassType : class
{
var classType = typeof(ClassType);
var existingClass = _injectedConcreteClasses.FirstOrDefault(x => x.Key == classType);
if (existingClass.Key != null)
{
throw new ArgumentException($"{injectedClass.GetType().Name} has been injected more than once");
}
_injectedConcreteClasses.Add(classType, injectedClass);
_fixture.Inject(injectedClass);
}
public ClassType GetInjectedMock<ClassType>() where ClassType : class
{
var classType = typeof(ClassType);
return _injectedConcreteClasses[classType] as ClassType;
}
/// <summary>
/// The target to be tested.
/// </summary>
public T Target => TargetMock.Object;
/// <summary>
/// The mock target to be tested.
/// </summary>
/// <summary>
/// The mock target to be tested.
/// </summary>
public Mock<T> TargetMock {
get
{
{
var mockTarget = _fixture.Create<Mock<T>>();
mockTarget.CallBase = true;
return mockTarget;
}
}
}
}
Let’s say we have this SampleService which has dependencies on ITestService and IMapper as below. The implementation of SampleService is just for testing the TestingContext and might not make too much sense but it’s not too important.
public interface ITestService
{
int Add(int num1, int num2);
}
public class AutoMapperProfileConfiguration : Profile
{
public AutoMapperProfileConfiguration() : this("AutoMapperProfileConfiguration")
{
}
protected AutoMapperProfileConfiguration(string profileName)
: base(profileName)
{
CreateMap<Article, ArticleDto>().ReverseMap();
}
}
public class SampleService
{
private readonly ITestService _testService;
private readonly IMapper _mapper;
public SampleService(ITestService testService, IMapper mapper)
{
_testService = testService;
_mapper = mapper;
}
public int Test()
{
var num1 = 5;
var num2 = 7;
var class1 = new Article
{
Id = Guid.NewGuid(),
Name = "ABC"
};
var class2 = _mapper.Map<ArticleDto>(class1);
var mapped = TestPublicVirtual();
return _testService.Add(num1, num2);
}
public virtual int TestPublicVirtual() => 1;
}
Testing SampleService without using TestingContext
Without using the TestingContext, my test would look like
[TestFixture]
public class UnitTestsWithoutTestingContext
{
private IMapper _mapper;
private Mock<ITestService> _testService;
private SampleService _target;
[SetUp]
public void SetUp()
{
_mapper = new MapperConfiguration(opts =>
{
opts.AddProfile<AutoMapperProfileConfiguration>();
}).CreateMapper();
_testService = new Mock<ITestService>();
_target = new SampleService(_testService.Object, _mapper);
}
[Test]
public void Test()
{
// Arrange
_testService.Setup(x => x.Add(It.IsAny<int>(), It.IsAny<int>())).Returns(10).Verifiable();
// Act
var result = _target.Test();
// Assert
_testService.Verify();
Assert.AreEqual(10, result);
}
[Test]
public void Test_With_Mock_Virtual_Method()
{
// Arrange
_testService.Setup(x => x.Add(It.IsAny<int>(), It.IsAny<int>())).Returns(10).Verifiable();
var target = new Mock<SampleService>(_testService.Object, _mapper);
target.Setup(x => x.TestPublicVirtual()).Returns(1000).Verifiable();
// Act
var result = target.Object.Test();
// Assert
_testService.Verify();
target.Verify();
Assert.AreEqual(10, result);
}
}
The problems we have here is that if the constructor of the target changes, we have to update all the tests which can be annoying.
Testing SampleService with using TestingContext
public class UnitTest : TestingContext<SampleService>
{
public override void SetUp()
{
var mapper = new MapperConfiguration(opts =>
{
opts.AddProfile<AutoMapperProfileConfiguration>();
}).CreateMapper();
InjectMock(mapper);
}
[Test]
public void Test()
{
// Arrange
GetMockFor<ITestService>().Setup(x => x.Add(It.IsAny<int>(), It.IsAny<int>())).Returns(10).Verifiable();
// Act
var result = Target.Test();
// Assert
GetMockFor<ITestService>().Verify();
Assert.AreEqual(10, result);
}
[Test]
public void Test_With_Mock_Virtual_Method()
{
// Arrange
GetMockFor<ITestService>().Setup(x => x.Add(It.IsAny<int>(), It.IsAny<int>())).Returns(10);
var target = TargetMock;
target.Setup(x => x.TestPublicVirtual()).Returns(1000).Verifiable();
// Act
var result = target.Object.Test();
// Assert
GetMockFor<ITestService>().Verify(x => x.Add(5,7), Times.Once);
Assert.AreEqual(10, result);
}
}
This will not require changing the tests if the constructor of the SampleService changes.
Conclusion
I now add this class to every new project in the future. It makes it easier to write and to maintain unit tests.