问题描述
我正在C#中学习和练习CQRS和单元测试。我需要知道如何在CQRS中对命令处理程序进行单元测试。
这是我的C:\>dir C:\etc\*.exe | find /i "timeout"
12/02/1999 03:54 PM 62,464 TIMEOUT.EXE
C:\>C:\etc\timeout /?
TIMEOUT - This utility is similar to the DOS PAUSE command. However,it
accepts a timeout parameter to specify a length of wait (in seconds)
at which time it will continue without a key press.
Written by Eric brown,Business System Division.
copyright (C) Microsoft Corporation,1992-1995.
Usage - TIMEOUT <###>
where <###> is a decimal number of seconds between -1 and 100000.
The value -1 means to wait indefinitely for a key hit.
:
CreateAuthorCommand
这是我的单元测试:
public sealed class CreateAuthorCommand : ICommand<Author>
{
public CreateAuthorCommand(string firstName,string lastName,DateTimeOffset dateOfBirth,string mainCategory,IEnumerable<CreateBookDto> books)
{
FirstName = firstName;
LastName = lastName;
DateOfBirth = dateOfBirth;
MainCategory = mainCategory;
Books = books;
}
public string FirstName { get; }
public string LastName { get; }
public DateTimeOffset DateOfBirth { get; private set; }
public string MainCategory { get; }
public IEnumerable<CreateBookDto> Books { get; }
[AuditLog]
[DatabaseRetry]
internal sealed class AddCommandHandler : ICommandHandler<CreateAuthorCommand,Author>
{
private readonly IUnitOfWork _unitOfWork;
public AddCommandHandler(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
}
public async Task<Result<Author>> Handle(CreateAuthorCommand command)
{
var nameResult = Name.Create(command.FirstName,command.LastName);
var birthDateResult = BirthDate.Create(command.DateOfBirth);
var mainCategoryResult = Entities.Authors.MainCategory.Create(command.MainCategory);
var authorResult = Result.Combine(nameResult,birthDateResult,mainCategoryResult)
.Map(() => new Author(nameResult.Value,birthDateResult.Value,null,mainCategoryResult.Value));
if (authorResult.IsFailure)
return Result.Failure<Author>(authorResult.Error);
await _unitOfWork.AuthorRepository.AddAsync(authorResult.Value);
await _unitOfWork.SaveChangesAsync();
return Result.Success(authorResult.Value);
}
}
}
当我调试上面的测试时,
- 我无法进入处理程序方法!
- 如何将
public class CreateAuthorCommandTests { [Fact] public void Create_Author_Should_Call_Add_Method_Once() { var fixture = new Fixture(); var command = fixture.Create<CreateAuthorCommand>(); var mockUnitOfWork = new Mock<IUnitOfWork>(); var mockHandler = new Mock<ICommandHandler<CreateAuthorCommand,Author>>(); var result = mockHandler.Object.Handle(command); mockUnitOfWork.Verify(x => x.AuthorRepository.AddAsync(It.IsAny<Author>()),Times.Once); } }
作为构造函数参数传递给mockUnitOfWork
?
如果我可以通过AddCommandHandler
,则可以验证mockUnitOfWork
内部的AddAsync
呼叫。请协助我做错事。
解决方法
您正在测试处理程序,所以代替此
var result = mockHandler.Object.Handle(command);
...创建一个AddCommandHandler的实际实例并注入其所需的依赖项,即
var mockUnitOfWork = new Mock<IUnitOfWork>();
var handler = new AddCommandHandler(mockUnitOfWork.Object);
调用 Handle 时,您现在将进入实现。
如果要保留“内部访问”修饰符,则可以使用InternalsVisibleTo属性授予对测试项目的访问权限,例如在定义处理程序的项目中执行此操作,
[assembly: InternalsVisibleTo(“UnitTests”)]
https://anthonygiretti.com/2018/06/27/how-to-unit-test-internal-classes-in-net-core-applications/