无法形成 Moq 测试来欺骗上下文访问

Can't form a Moq test to spoof context access

对一般的最小起订量和模拟测试完全陌生。我正在尝试遵循一个教程,但它适合我的需要,这是通过 entityFrameworkCore 上下文欺骗一些数据库访问。如何设置和测试我的数据库的响应 returning 0 或任意数字?

为了澄清,我想测试向我的 DoSomething 方法提供 "Whatever" 将 return 0,并且提供任何其他字符串将生成一个条目 ID。当然,Id 取决于现实生活中的数据库增量,所以我只需要设置一个任意数字作为响应并测试这是 returned。当然,这是我的真实方法的一个非常缩小的例子。

我已经尽可能地减少了设置:

接口:

public interface ITestClass
{
    int DoSomething(string thing);
}

实施:

public class TestClass : ITestClass
{
    private readonly TestContext _testContext;
    public TestClass(TestContext testContext)
    {
        _testContext = testContext;
    }
    public int DoSomething(string thing)
    {
        if (thing == "Whatever") return 0;
        Item i = new Item()
        {
            Thing = thing
        };
        _testContext.Add(i);
        _testContext.SaveChanges();
        return i.Id;
    }
}

上下文:

public class TestContext : DbContext
{
    public TestContext(DbContextOptions<TestContext> options) : base(options) { }
}

Table/型号class:

public class Item
{
    public int Id { get; set; }
    public string Thing { get; set; }
}

我忽略了连接字符串,因为重点是在不连接到数据库的情况下测试方法,对吧?最后,这是我对嘲笑的尝试,我对此一无所知,tbh:

    public void Test1()
    {
        var mock = new Mock<ITestClass>();
        mock.Setup(m => m.DoSomething("Whatever"));
        // Assert returns 0
        mock.Setup(m => m.DoSomething("ValidString"));
        // Assert returns arbitrary 12345 - where do I spoof this number?
    }

我认为您对 mocking 的用法存在根本性的误解。让我们看看你的测试

public void Test1()
    {
        var mock = new Mock<ITestClass>();
        mock.Setup(m => m.DoSomething("Whatever"));
        // Assert returns 0
        mock.Setup(m => m.DoSomething("ValidString"));
        // Assert returns arbitrary 12345 - where do I spoof this number?
    }

现在,当您在 mock 上使用 Setup 时,您还可以使用 Returns、ReturnsAsync 或 Throws 来告诉 mock 您希望它做什么 return提供这些值。所以你的 DoSomething 设置应该是这样的

mock.Setup(m => m.DoSomething(It.Is<string>(i => i == "ValidString"))).Returns(12345);

不过这里应该有一个明显的问题。如果我们明确地告诉它对于给定的输入要给我们什么,那么我们真正测试的是什么?那时只是模拟本身。这不是模拟的用途。所以一条规则,永远不要嘲笑你正在测试的东西。相反,你模拟它的 dependencies。在这种情况下,唯一的一个是 TestContext。相反,我们实际上想要测试 DoSomething 方法,所以我们创建了一个 class 的真实实例来测试。

你没有指定你使用的是哪个测试框架,所以我用 NUnit 写了我的

        [Test]
        public void Test1()
        {
            // Mock the dependency
            Mock<ITestContext> mockContext = new Mock<ITestContext>();
            mockContext.Setup(m => m.Add(It.IsAny<Item>()))
                .Returns(true);
            mockContext.Setup(m => m.SaveChanges())
                .Returns(true);

            // Inject the dependency
            TestClass testClass = new TestClass(mockContext.Object);

            int result = testClass.DoSomething("Whatever");

            // Verify the methods on the mock were called once
            mockContext.Verify(m => m.Add(It.IsAny<Item>()), Times.Once);
            mockContext.Verify(m => m.SaveChanges(), Times.Once);

            // Assert that the result of the operation is the expected result defined elsewhere
            Assert.That(result, Is.EqualTo(ExpectedResult));
        }

测试模拟 return 您将其设置为 return 的内容没有任何价值。我们想测试真正的交易,当依赖关系很复杂时,模拟只会使这成为可能。它们是真实系统在实践中使用的后端系统和复杂对象图的替代品,仅此而已。神奇的是,它允许我们只设置我们需要的 mock 部分,具体的函数调用和 属性 访问依赖于它的 class 所依赖的,而不必担心设置整个 class 及其所有依赖项等等。