I want to unit test my function GetDogName
which creates a new DbContext
. I already have a working Mock
of my DbContext
, but I can't pass it everywhere as a parameter.
I can replace the DbContext context = new()
with something else in the function. But I don't have the possibility to pass the DbContext
as a parameter.
I also have checked if it is possible with a factory (or something else?), but have not found a solution yet.
static class AnimalsHelper
{
public static string GetDogName() // I can not pass parameters here
{
// I can change this line
using (DbContext context = new())
{
return context.Dogs.First().Name;
}
}
}
class UnitTest
{
[SetUp]
public void SetUp()
{
ContextMock = new Mock<DbContext>();
Context = ContextMock.Object;
ContextMock.Setup(context => context.Dogs).Returns("Rex");
}
[Test]
public void GetDogName_DogName_ReturnsRex()
{
var expected = "Rex";
var actual = AnimalsHelper.GetDogName();
Assert.AreEqual(expected, actual);
}
}
We have solved this with a DbContextCreator
class.
An instance of a IDbContextCreator
class is created in the Main
and this is then kept static.
The function to which a DbContext
can not be passed as a parameter uses this static
creator instance to create a new DbContext
.
In the unit test, a creator class is used, which returns a DbContextMock
.
This allows the function to be tested without accessing the database.
internal static class Program
{
/// <summary>
/// Main application entry point
/// </summary>
internal static async Task Main(string[] args)
{
DbContextCreator.Instance = new MyDbContextCreator();
// Start Application
}
/// <inheritdoc />
private class MyDbContextCreator : IDbContextCreator
{
/// <inheritdoc />
public BaseDbContext Create() => new();
}
}
public static void TestableFunction()
{
using (BaseDbContext context = DbContextCreator.Create())
{
// DB stuff
context.SaveChanges();
}
}
public abstract class AbstractTestWithMockData : AbstractTest
{
protected BaseDbContext Context { get; set; }
protected Mock<BaseDbContext> ContextMock { get; set; }
/// <inheritdoc />
protected AbstractTestWithMockData()
{
DbContextCreator.Instance = new TestDbContextCreator(this);
}
/// <inheritdoc />
[SetUp]
public override void SetUp()
{
base.SetUp();
ContextMock = new Mock<BaseDbContext>();
Context = ContextMock.Object;
// ContextMock.Setup()
}
/// <inheritdoc />
private class TestDbContextCreator : IDbContextCreator
{
private readonly AbstractTestWithMockData AbstractTest;
public TestDbContextCreator(AbstractTestWithMockData abstractTest)
{
AbstractTest = abstractTest;
}
/// <inheritdoc />
public BaseDbContext Create()
{
return AbstractTest.Context;
}
}
}
Interface
and static
Instance
/// <summary>
/// Interface for a project-specific implementation of a <see cref="BaseDbContext" /> creator.<br />
/// The creator is required in order to be able to unit test functions
/// where a <see cref="BaseDbContext" /> cannot be passed as a parameter.
/// </summary>
public interface IDbContextCreator
{
BaseDbContext Create();
}
/// <summary>
/// Static database creator that holds an instance of <see cref="IDbContextCreator" />
/// to be able to create an instance of <see cref="BaseDbContext" />.
/// </summary>
public static class DbContextCreator
{
/// <summary>
/// Static instance of <see cref="IDbContextCreator" /> for the <see cref="Create" /> function.
/// </summary>
[SuppressMessage("Design", "CA1044:Properties should not be write only")]
public static IDbContextCreator? Instance { private get; set; }
/// <summary>
/// Creates a new instance of <see cref="BaseDbContext" />.
/// </summary>
/// <returns>A new instance of <see cref="BaseDbContext" /></returns>
/// <exception cref="Exception">When the <see cref="Instance" /> is not set</exception>
public static BaseDbContext Create()
{
if (Instance is null)
{
throw new Exception("Database creator instance is not set! Covfefe!");
}
return Instance.Create();
}
}