Gur*_*ler 5 c# unit-testing domain-driven-design clean-architecture
我正在尝试学习 DDD 和 Clean Architecture 的一些想法,但遇到了一个问题:如何对应用程序层中的命令处理程序进行单元测试,验证它们在域对象上调用正确的方法,而不测试内部的逻辑那些域对象?假设我有以下域实体:
public class User
{
public User(int id)
{
Id = id;
}
public int Id { get; }
public void RemoveProfilePicture()
{
...
}
...
}
Run Code Online (Sandbox Code Playgroud)
我有以下简单的命令类:
public class RemoveUserProfilePictureCommand : ICommand
{
public RemoveUserProfilePictureCommand(int userId)
{
UserId = userId;
}
public int UserId { get; }
}
Run Code Online (Sandbox Code Playgroud)
和命令处理程序(位于应用程序层):
public class Handler : ICommandHandler<RemoveUserProfilePictureCommand>
{
private readonly IUserRepository userRepository;
public Handler(IUserRepository userRepository)
{
this.userRepository = userRepository;
}
public void Handle(RemoveUserProfilePictureCommand command)
{
var user = userRepository.GetById(command.UserId);
user.RemoveProfilePicture();
}
}
Run Code Online (Sandbox Code Playgroud)
我想验证对的调用Handle是否会找到正确的并调用其上的User域方法。RemoveProfilePicture我发现的仅有的两个选项并不能让我满意,我正在寻找更好的解决方案。
显而易见的解决方案是断言域模型上的操作发生了,例如:
[Fact]
public void Handle_UserExists_ShouldRemoveProfilePicture()
{
var user = new User(id: 555);
repository.GetById(user.Id).Returns(user);
var command = new RemoveUserProfilePictureCommand(user.Id);
handler.Handle(command);
Assert.Null(user.ProfilePicture);
}
Run Code Online (Sandbox Code Playgroud)
这种方法的问题是,我们根据域模型内部的逻辑进行断言,如果该逻辑比将属性设置为更复杂,ProfilePicture我们null仍然必须在命令处理程序的测试中断言其结果,即使领域逻辑已经被它自己的单元测试覆盖。问题源于应用层类与域类的紧密耦合。这导致我想到了第二个解决方案:
如果该类User将实现一个接口,例如IUser,那么测试中的假存储库可能会IUser向命令处理程序返回不同的实现,以验证调用了正确的方法。这里的问题是,根据我的理解,应用程序层应该是域的薄包装,并且不应该与其分离。另外,我发现的所有示例始终使用领域对象的具体类型,并且在实体类中实现接口似乎很奇怪。
有人能找到更好的解决方案来测试此类吗?因为它不是关于某些边缘情况,而是关于几乎每个命令处理程序类,并且其中大多数比我上面给出的简单示例更复杂。
当我做 DDD 启发的 CQRS 项目时,我也遇到了完全相同的问题。据我所知,您还尝试开发一个 CQRS 项目,因为您正在使用命令等概念,并且坚持null在命令处理程序中返回一个值。
话虽如此,断言一个null值并不是测试任何东西。您要测试的是系统中是否发生了副作用。就您而言,您希望确保“用户个人资料图片已被删除”。
您似乎缺少的是 CQRS 的另一半:事件。当命令处理程序成功完成其事务(即副作用)时,您通常会调度一个代表该事务的事件。然后,您的单元测试将简单地断言UserProfilePictureRemoved事件已调度(或排队,具体取决于您的实现)。