我一直致力于使用 Moq 为我们的一些 CQRS 命令设置单元测试。 我已经模拟了一个在我的环境中运行的命令处理程序来运行命令。
我遇到的问题是单元测试部分。当尝试模拟 AddAsync 调用时,我不断在
newUser
函数中获取 RaiseDomainEvent
的空值。
这是命令和处理程序的设置,基本上意思是如果有任何设置,则引发域事件。
用户/用户设置实体——尝试在这里进行丰富的域建模
public class User
{
private User(int id, string username)
{
UserId = id;
UserName = username;
}
public int UserId {get; private set;}
public string UserName {get; private set;}
public List<UserSetting> UserSettings {get; private set;}
public static User Create(int id, string username)
{
return new User(id, username);
}
public static DomainEvent RaiseDomainEvent(User user, List<UserSetting> settings)
{
// Raise the event and handle it
}
}
public class UserSetting
{
public int SettiingId {get; set;}
public int UserId {get; set;}
public int SettingTypeId {get; set;}
public string SettingValue {get; set;}
}
添加用户命令
public sealed record AddUserCommand (
int UserId, string UserName, List<UserSetting> UserSettings
) : ICommand;
添加用户命令处理程序 - 利用 Mediatr
internal sealed class AddUserCommandHandler : ICommandHandler<AddUserCommand>
{
private readonly IMapper _mapper;
private readonly IUserRepo _userRepo;
public AddUserCommandHandler(IMapper mapper, IUserRepo userRepo)
{
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
_userRepo = userRepo ?? throw new ArgumentNullException(nameof(userRepo));
}
public async Task<Result> Handle(AddUserCommand cmd, CancellationToken ct = default)
{
User dbUser = await _userRepo.GetUserByIdAsync(cmd.UserId, ct);
if (dbUser != null) throw new Exception($"User exists with id: {cmd.UserId}");
User newUser = User.Create(cmd.UserId, cmd.UserName);
// Since the id is generated on the DB add, we need to get it to add a FK relationship to add settings to it
newUser = await _userRepo.AddAsync(newUser, ct);
if (cmd.UserSettings != null && cmd.UserSettings.Any())
{
User.RaiseDomainEvent(newUser, cmd.UserSettings);
}
return new Result
{
Success = true
};
}
}
添加异步
public async Task<User> AddAsync(User entity, CancellationToken ct = default)
{
_context.Users.Add(entity);
await _context.SaveChangesAsync(ct);
return entity;
}
上面的代码可以发挥其实际作用
问题来自于我尝试对其进行单元测试时。
我利用最小起订量对其进行单元测试。
public class AddUserCommandHandlerTests
{
private readonly Mock<IMapper> _mapperMock;
private readonly Mock<IUserRepo> _userRepoMock;
public AddUserCommandHandlerTests()
{
_mapperMock = new Mock<IMapper>();
_userRepoMock = new Mock<IUserRepo>();
}
// This test works!
[Fact]
public async Task Handle_Should_Return_SuccessResult_NoSettings()
{
AddUserCommand cmd = new AddUserCommand(0, "stacktest", new List<UserSetting>());
User mockDbUser = null;
_userRepoMock.Setup(x => x.GetUserByIdAsync(cmd.UserId, default).Result).Returns(mockDbUser);
var handler = new AddUserCommandHandler(_mapperMock.Object, _userRepoMock.Object);
var result = await handler.Handle(cmd, default);
Assert.True(result.Success);
}
// This test fails, saying that newUser is null at User.RaiseDomainEvent(newUser, cmd.UserSettings)
[Fact]
public async Task Handle_Should_Return_SuccessResult_WithSettings()
{
AddUserCommand cmd = new AddUserCommand(0, "stacktest", new List<UserSetting> { SettingId = 0, SettingTypeId = 1, UserId = 0, SettingValue = "Test"});
User mockDbUser = null;
User addedDbUser = User.Create(10, cmd.UserName);
_userRepoMock.Setup(x => x.GetUserByIdAsync(cmd.UserId, default).Result).Returns(mockDbUser);
_userRepoMock.Setup(x => x.AddAsync(mockDbUser, default).Result).Returns(addedDbUser);
var handler = new AddUserCommandHandler(_mapperMock.Object, _userRepoMock.Object);
var result = await handler.Handle(cmd, default);
Assert.True(result.Success);
}
}
为什么 AddAsync 结果没有正确起订量?
您的
AddAsync
设置不正确。
您正在将其注册为使用
mockDbUser
进行调用,但是 Handle
不会使用 AddAsync
来调用 mockDbUser
。由于 Handle
用于调用 AddAsync
的实际对象是在 Handle
本身内生成的,因此您将无权访问它。因此,当 Handle
在单元测试中调用 AddAsync
时,它会忽略您的设置,因为参数与您设置中的参数不匹配。
您可以使用 Moq
It
方法解决该问题。 It.IsAny
允许您将给定类型的任何参数指定为正在设置的方法的参数:
_userRepoMock.SetUp(x => x.AddAsync(It.IsAny<User>(), default).Result).Returns(addedDbUser);
It.Is
允许您指定满足某些选定条件的给定类型的任何参数作为正在设置的方法的参数,例如:
_userRepoMock.SetUp(x => x.AddAsync(It.Is<User>(x => x.UserId == 10 && x.UserName == cmd.UserName), default).Result).Returns(addedDbUser);