在不调用基础服务的情况下设置Mock返回值
本文关键字:情况下 设置 Mock 返回值 服务 调用 | 更新日期: 2023-09-27 18:04:28
让我们假设我们有我想测试的PaymentService
:
public interface IPaymentService
{
int Pay(int clientId);
}
public class PaymentService : IPaymentService
{
// Insert payment and return PaymentID
public int Pay(int clientId)
{
int storeId = StaticContext.Store.CurrentStoreId; // throws NullReferenceException
// ... other related tasks
}
}
public class Payment_Tests
{
[Test]
public void When_Paying_Should_Return_PaymentId
{
// Arrange
var paymentServiceMock = new Mock<IPaymentService>();
paymentService.Setup(x=>x.Pay(Moq.It.IsAny<int>).Returns(999); // fails because of NullReferenceException inside Pay method.
// Act
var result = paymentService.Object.Pay(123);
// Asserts and rest of the test goes here
}
}
但我无法模拟StaticContext类我无法重构它,也无法通过构造函数将此类注入IPaymentService
-这是旧代码,必须保持原样:(
有没有可能简单地返回预期的结果,在我的情况下是999
,而不调用底层StaticContent.Store.CurrentStoreId?
编辑:我知道目前这个测试没有意义,但我想知道是否有办法以我要求的方式做到这一点。这只是我问题的简化版本。
不,您不能使用它来测试服务。看看在MSTests或Fakes中使用Moles(如果可以的话(。
你必须创建一个假的程序集:
using (ShimsContext.Create())
{
var paymentServiceMock = new Mock<IPaymentService>();
paymentService.Setup(x=>x.Pay(Moq.It.IsAny<int>).Returns(999);
// Shim DateTime.Now to return a fixed date:
System.Fakes.ShimDateTime.StaticContext.Store.CurrentStoreIdGet = () => { 1 };
// Act
var result = paymentService.Object.Pay(123);
}
Mock对象是Moq为您正在模拟的接口生成的代理类。所以,当你练习模拟对象时
var result = paymentService.Object.Pay(123);
实际上,您正在验证Moq框架的实现——它是否返回您为mock设置的结果。我认为您不想对Moq框架进行单元测试。若您正在为PaymentService
类编写测试,那个么您应该练习这个类的实例。但它内部有静态依赖关系。所以,第一步将使PaymentService
可测试——即用抽象替换静态依赖关系,并将这些抽象注入PaymentService
实例。
public interface IStore
{
int CurrentStoreId { get; }
}
然后使PaymentService
依赖于这个抽象:
public class PaymentService : IPaymentService
{
private IStore _store;
public PaymentService(IStore store)
{
_store = store;
}
public int Pay(int clientId)
{
int storeId = _store.CurrentStoreId;
// ... other related tasks
}
}
因此,现在不存在静态依赖关系。下一步是编写PaymentService
的测试,它将使用模拟依赖项:
[Test]
public void When_Paying_Should_Return_PaymentId
{
// Arrange
var storeMock = new Mock<IStore>();
storeMock.Setup(s => s.CurrentStoreId).Returns(999);
var paymentService = new PaymentService(storeMock.Object);
// Act
var result = paymentService.Pay(123);
storeMock.Verify();
// Asserts and rest of the test goes here
}
最后是IStore
抽象的实际实现。您可以创建类,将调用委托给静态StaticContext.Store
:
public class StoreWrapper : IStore
{
public int CurrentStoreId
{
get { return StaticContext.Store.CurrentStoreId; }
}
}
在实际应用程序中设置依赖关系时使用此包装。
public interface IPaymentService
{
int Pay(int clientId);
}
public interface IStore
{
int ID { get; }
// Returns the payment ID of the payment you just created
// You would expand this method to include more parameters as
// necessary
int CreatePayment();
}
public class PaymentService : IPaymentService
{
private readonly IStore _store;
public PaymentService(IStore store)
{
_store = store;
}
// Insert payment and return PaymentID
public int Pay(int clientId)
{
//int storeId = StaticContext.Store.CurrentStoreId;
// Static is bad for testing and this also means you're hiding
// Payment Service's dependencies. Inject a store into the constructor
var storeId = _store.ID;
// stuff
....
return _store.CreatePayment();
}
}
public class Payment_Tests
{
[Test]
public void When_Paying_Should_Return_PaymentId
{
// Arrange
var store = new Mock<IStore>();
var expectedId = 42;
store.Setup(x => x.CreatePayment()).Returns(expectedId);
var service = new PaymentService(store);
// Act
var result = paymentService.Pay(123);
// Asserts and rest of the test goes here
Assert.Equal(expectedId, result);
}
}
将IStore
对象注入PaymentService
-使用StaticContext
是关于PaymentService
的依赖关系的,违反了最小惊喜原则(开发人员试图使用PaymentService,然后意识到他们必须在抛出异常和一些挖掘(例如,通过注入依赖关系在构造函数中没有记录(后做其他事情(,这使得测试更加困难(正如您所注意到的,StaticContext.Store
为空,因为它还没有设置(,并且灵活性降低。
之后,您将告诉Store从CreatePayment
返回某个值,并测试该服务是否返回相同的值(即支付ID(
编辑:
我无法重构它,也无法通过构造函数将此类注入IPaymentService-这是旧代码,必须保持原样:(
关于这个注释,在这种情况下,你能做的最好的事情就是将StaticContext.Store
值设置为一个伪造的Store对象,该对象返回一个硬编码的数字,并测试。。。但实际上,您应该重构这段代码,因为从长远来看,这会让它变得容易得多。
// inside test code
// obviously change the type as necessary
// as C# doesn't have ducktyping
class FakedStore
{
public int CurrentStoreId { get { return 42; } }
}
var store = new FakedStore();
StaticContext.Store = store;
// rest your test to test the payment service
var result = ..
Assert.Equals(result, store.CurrentStoreId)