C# 如何模拟 DbContext 而不传递它

问题描述 投票:0回答:2

我想对我的函数

GetDogName
进行单元测试,这将创建一个新的
DbContext
。我已经有了我的
Mock
的工作
DbContext
,但我无法将它作为参数传递到任何地方。

我可以用函数中的其他内容替换

DbContext context = new()
。但我无法将
DbContext
作为参数传递。

我也检查过工厂是否可行(或其他什么?),但尚未找到解决方案。

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);
    }
}
c# entity-framework unit-testing dbcontext
2个回答
0
投票
public class AnimalDbContext : DbContext
{
    // !!! Must be virtual
    public virtual DbSet<Dog> Dogs { get; set; }
}

public class AnimalService
{
    private AnimalDbContext _context;

    public AnimalService(AnimalDbContext context) 
    {
        _context = context;
    }

    public string GetDogName()
    {
        return _context.Dogs.FirstOrDefault()?.Name;
    }
}

[TestClass()]
public class AnimalServiceTests
{
    [TestMethod()]
    public void GetDogName_DogName_ReturnsRex()
    {
        var data = new List<Dog>
        {
            new Dog { Name = "Rex" }
        }.AsQueryable();

        var mockSet = new Mock<DbSet<Dog>>();
        mockSet.As<IQueryable<Dog>>().Setup(m => m.Provider).Returns(data.Provider);
        mockSet.As<IQueryable<Dog>>().Setup(m => m.Expression).Returns(data.Expression);
        mockSet.As<IQueryable<Dog>>().Setup(m => m.ElementType).Returns(data.ElementType);
        mockSet.As<IQueryable<Dog>>().Setup(m => m.GetEnumerator()).Returns(() => data.GetEnumerator());

        var mockContext = new Mock<AnimalDbContext>();
        mockContext.Setup(c => c.Dogs).Returns(mockSet.Object);

        var service = new AnimalService(mockContext.Object);
        
        // Arrange
        var dogName = service.GetDogName();

        var actual = "Rex";

        Assert.AreEqual(actual, dogName);
    }
}

0
投票

我们已经通过

DbContextCreator
类解决了这个问题。

IDbContextCreator
中创建
Main
类的实例,然后保持静态。
DbContext
不能作为参数传递到的函数使用此 static 创建者实例来创建新的
DbContext
在单元测试中,使用了一个创建者类,它返回一个
DbContextMock
这允许在不访问数据库的情况下测试功能。

主要

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

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();
    }
}

© www.soinside.com 2019 - 2024. All rights reserved.