C# / Moq - 如何在一步中强制异常和 return 一个值
C# / Moq - How to force an exception and return a value in one step
我有一个使用以下方法的存储库 DoSomeWork:
internal class MyRepository : IMyRepository
{
public MyRepository(ILogger<MyRepository> logger, IDbContextWrapper dbContext)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
}
public Task<Result> DoSomeWork(int someInt)
{
return Task.Run(() =>
{
try
{
var parameters = new DynamicParameters(new { SomeIntValue = someInt });
parameters.Add("WorkComplete", DbType.Boolean, direction: ParameterDirection.ReturnValue);
dbContext.TransactionedExecute("NameOfStoredProcedure", parameters, CommandType.StoredProcedure); //This is a wrapper for Dapper (DbConnection)
var status = (DoSomeWorkStatus)parameters.Get<int>("WorkComplete");
var workComplete = status == DoSomeWorkStatus.DoneSuccessfully;
return workComplete ? Result.WorkDone : Result.NoWorkDone;
}
catch(DatabaseTimeoutException dte)
{
logger.LogInformation(dte, "");
return Result.Error;
}
catch(DatabaseDeadlockException dde)
{
logger.LogInformation(dde, "");
return Result.Error;
}
});
}
}
我想要实现的是测试和验证一旦 DatabaseTimeoutException
或 DatabaseDeadlockException
被捕获在 try/catch 中,任务应该 return Result.Error
。所有这一切都应该一步完成(无需重试)。
在测试中我有以下内容:
private Mock<IMyRepository> myRepoMock;
private MyRepoManager target;
...
[SetUp]
public void SetUp()
{
myRepoMock = new Mock<IMyRepository>();
target = new MyRepoManager(myRepoMock.Object);
}
[Test]
public async Task MyMoqTest()
{
//Arrange
myRepoMock
.Setup(mrm => mrm.DoSomeWork(It.IsAny<int>()))
.Returns(Task.FromException<Result>(new DatabaseTimeoutException()));
//myRepoMock
// .Setup(mrm => mrm.DoSomeWork(It.IsAny<int>()))
// .Throws<DatabaseTimeoutException>(); <- The same result as above
//Act
Result taskResult = await target.RunTask(int someInt); //Calls repository method - DoSomeWork
//Assert
Assert.AreEqual(Result.Error, taskResult.Result);
}
但是发生的事情是存储库抛出 DatabaseTimeoutException
而没有 returning Result.Error
,并且测试失败并显示消息(如预期的那样):
MyExceptions.DatabaseTimeoutException : Exception of type 'MyExceptions.DatabaseTimeoutException' was thrown.
我对 Moq 很陌生,所以我的问题是 - 这可以用 Moq 完成吗?如果可以,我将如何做?
谢谢。
单元测试最重要的部分是识别被测系统 (SUT)。这就是您实际要验证的东西。一旦你确定了这一点,你的 SUT 的所有依赖项都应该被模拟,这样你就可以严格控制你正在测试的东西之外的一切。
如果您正在尝试进行单元测试 MyRepoManager.RunTask,那么它不应该关心其依赖项的任何内部实现细节。它应该只关心他们公开的合同。在这种情况下,您依赖于 IMyRepository。所以具体实现 MyRepository 做什么是无关紧要的。 MyRepository 可能会在内部处理 DatabaseTimeoutException 和 DatabaseDeadlockException,但这是一个实现细节,而不是通过 IMyRepository 定义的契约的一部分。目标是模拟依赖项的行为,而不是在模拟框架内完全重新实现依赖项的内部行为。
所以,您的模拟设置应该是:
myRepoMock
.Setup(mrm => mrm.DoSomeWork(It.IsAny<int>()))
.Returns(Task.FromResult(Result.Error));
我有一个使用以下方法的存储库 DoSomeWork:
internal class MyRepository : IMyRepository
{
public MyRepository(ILogger<MyRepository> logger, IDbContextWrapper dbContext)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
}
public Task<Result> DoSomeWork(int someInt)
{
return Task.Run(() =>
{
try
{
var parameters = new DynamicParameters(new { SomeIntValue = someInt });
parameters.Add("WorkComplete", DbType.Boolean, direction: ParameterDirection.ReturnValue);
dbContext.TransactionedExecute("NameOfStoredProcedure", parameters, CommandType.StoredProcedure); //This is a wrapper for Dapper (DbConnection)
var status = (DoSomeWorkStatus)parameters.Get<int>("WorkComplete");
var workComplete = status == DoSomeWorkStatus.DoneSuccessfully;
return workComplete ? Result.WorkDone : Result.NoWorkDone;
}
catch(DatabaseTimeoutException dte)
{
logger.LogInformation(dte, "");
return Result.Error;
}
catch(DatabaseDeadlockException dde)
{
logger.LogInformation(dde, "");
return Result.Error;
}
});
}
}
我想要实现的是测试和验证一旦 DatabaseTimeoutException
或 DatabaseDeadlockException
被捕获在 try/catch 中,任务应该 return Result.Error
。所有这一切都应该一步完成(无需重试)。
在测试中我有以下内容:
private Mock<IMyRepository> myRepoMock;
private MyRepoManager target;
...
[SetUp]
public void SetUp()
{
myRepoMock = new Mock<IMyRepository>();
target = new MyRepoManager(myRepoMock.Object);
}
[Test]
public async Task MyMoqTest()
{
//Arrange
myRepoMock
.Setup(mrm => mrm.DoSomeWork(It.IsAny<int>()))
.Returns(Task.FromException<Result>(new DatabaseTimeoutException()));
//myRepoMock
// .Setup(mrm => mrm.DoSomeWork(It.IsAny<int>()))
// .Throws<DatabaseTimeoutException>(); <- The same result as above
//Act
Result taskResult = await target.RunTask(int someInt); //Calls repository method - DoSomeWork
//Assert
Assert.AreEqual(Result.Error, taskResult.Result);
}
但是发生的事情是存储库抛出 DatabaseTimeoutException
而没有 returning Result.Error
,并且测试失败并显示消息(如预期的那样):
MyExceptions.DatabaseTimeoutException : Exception of type 'MyExceptions.DatabaseTimeoutException' was thrown.
我对 Moq 很陌生,所以我的问题是 - 这可以用 Moq 完成吗?如果可以,我将如何做?
谢谢。
单元测试最重要的部分是识别被测系统 (SUT)。这就是您实际要验证的东西。一旦你确定了这一点,你的 SUT 的所有依赖项都应该被模拟,这样你就可以严格控制你正在测试的东西之外的一切。
如果您正在尝试进行单元测试 MyRepoManager.RunTask,那么它不应该关心其依赖项的任何内部实现细节。它应该只关心他们公开的合同。在这种情况下,您依赖于 IMyRepository。所以具体实现 MyRepository 做什么是无关紧要的。 MyRepository 可能会在内部处理 DatabaseTimeoutException 和 DatabaseDeadlockException,但这是一个实现细节,而不是通过 IMyRepository 定义的契约的一部分。目标是模拟依赖项的行为,而不是在模拟框架内完全重新实现依赖项的内部行为。
所以,您的模拟设置应该是:
myRepoMock
.Setup(mrm => mrm.DoSomeWork(It.IsAny<int>()))
.Returns(Task.FromResult(Result.Error));