在单元测试(JUnit 5)中是否需要断言模拟对象和方法?

Is asserting mocked object and method necessary in Unit Test(JUnit 5)?

我仍然是单元测试的基础学习者。

我试图在不直接调用它的情况下模拟我的 class 方法(因为它依赖于第 3 方库)并且我编写了如下所示的测试方法。

(我提到的第 3 方库不是 MapStruct,它是 ModelObject.class,它的构造函数参数非常复杂,只能在库级别初始化)

ProjectMapperclass的toDto方法是一个简单的对象映射方法(带有MapStruct库)。

@Test
@DisplayName("Should convert Project ModelObject to Project DTO successfully.")
void testProjectModelObjectToDtoSucess() {
    // Given 
    ModelObject mockModelObject = mock(ModelObject.class); // <-- Mocking "ModelObject.class" 

    ProjectDto expected = new ProjectDto("PPJT-00000001", "Test Project 01"); // <-- Initializing expected return object.

    when(mockProjectMapper.toDto(mockModelObject)).thenReturn(expected); // <-- Mocking cut(class under test)'s method. 

    // When 
    ProjectDto actual = mockProjectMapper.toDto(mockModelObject); // <-- Calling mocked method. 

    // Then 
    assertEquals(actual.getId(), expected.getId(), "The converted DTO's ID should equal to expected DTO's ID.");
    assertEquals(actual.getName(), expected.getName(), "The converted DTO's name should equal to expected DTO's name.");
}

我想知道的是,如果我已经假设 mockProjectMapper.toDto() 将 return 正好等于 expected 对象,为什么我需要用 actual(returned) 对象?

我了解到单元测试应该测试任何可破解的代码。

我怀疑这种测试方式有什么好处,如果不合适,用 mocking 测试这种方法的正确方法是什么?

我参考了this example code的这个测试方法。

@refael-sheinker 这是 ProjectMapper class 的源代码,它是 MapStruct 的界面 class.

@Mapper
public interface ProjectMapper {
    ProjectMapper INSTANCE = Mappers.getMapper(ProjectMapper.class);

    @Mapping(target = "id", source = "projectId")
    @Mapping(target = "name", source = "projectName")
    ProjectDto toDto(ModelObject modelObject);
}

您模拟了被测对象并存根了被测方法 - 您正确地认为这样的测试不会带来任何好处。

您正在检查您是否可以存根方法,而不是您的生产代码。

主要问题是您假设需要模拟每个第 3 方 class。这是错误的思维方式 - 你应该尝试模拟你的 class 的合作者(尤其是那些导致 side-effects 在测试环境中不需要的),而不是 classes 是关键部分class 的执行情况。该行有时可能会模糊,但在这个具体示例中,我强烈建议不要模拟映射器:

  • 您的第 3 方 class 由注释驱动
  • 注释必须与您的 DTO 字段相对应
  • 注释中的字段名称是字符串,它们的存在是编译器不强制执行的 DTO
  • 所有映射都在内存中完成

创建一个运行生产代码的测试让您有信心:

  • 字段名称正确
  • 相应的映射字段具有正确的类型(例如,您没有将 int 映射到 boolean)

服务的链接测试有些不同:它测试调用存储库的 Spring 服务。存储库可能代表一些与外界交互的数据库——它是一个很好的模拟对象。在我看来,服务方法只将调用转发到存储库——这是一个微不足道的行为——所以测试是微不足道的,但这个设置将扩展到更复杂的服务方法。

更新

MapStruct 的模拟源 class。

如果 ModelObject 创建起来很复杂,请考虑对其某些构造函数参数使用模拟。

var pN = mock(ParamN.class);
// possibly stub some interactions with pN
var modelObject = new ModelObject(10, 20, pN);

或者,您甚至可以模拟整个 ModelObect 并将其 getter 存根:

var mockModelObject = mock(ModelObject.class); 
when(mockModelObject.getId()).thenReturn(10);