无法形成 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 及其所有依赖项等等。
对一般的最小起订量和模拟测试完全陌生。我正在尝试遵循一个教程,但它适合我的需要,这是通过 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 及其所有依赖项等等。