如何干净地测试使用 DomainClassConverter 检索参数的 Spring 控制器?

le-*_*ude 2 java spring unit-testing spring-mvc mockito

我热衷于干净、隔离良好的单元测试。但我在这里偶然发现了“干净”部分,用于测试控制器,该控制器使用DomainClassConverter功能来获取实体作为其映射方法的参数。

@Entity
class MyEntity {
    @Id
    private Integer id;
    // rest of properties goes here.
}
Run Code Online (Sandbox Code Playgroud)

控制器是这样定义的

@RequestMapping("/api/v1/myentities")
class MyEntitiesController {
    @Autowired
    private DoSomethingService aService;

    @PostMapping("/{id}")
    public ResponseEntity<MyEntity> update(@PathVariable("id")Optional<MyEntity> myEntity) {
        // do what is needed here
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,从小DomainClassConverter文档我知道它用于CrudRepository#findById查找实体。我想知道的是如何在测试中干净地模拟它。我通过执行以下步骤取得了一些成功:

  1. 创建一个我可以模拟的自定义转换器/格式化程序
  2. 使用上面的转换器实例化我自己的 MockMvc
  3. 在每次测试时重置模拟并更改行为。

问题是设置代码很复杂,因此很难调试和解释(我的团队 99% 都是来自 Rails 或 Uni 的初级人员,所以我们必须保持简单)。我想知道是否有一种方法可以MyEntity从单元测试中注入所需的实例,同时继续使用@Autowired MockMvc.

CrudRepository目前我正在尝试是否可以注入for的模拟,MyEntity但没有成功。我已经有几年没有使用 Spring/Java 了(4),所以我对可用工具的了解可能不是最新的。

Raf*_*eco 5

因此,从 DomainClassConverter 小文档中我知道它使用 CrudRepository#findById 来查找实体。我想知道的是如何在测试中干净地模拟它。

您将需要模拟在 之前调用的 2 个方法,CrudRepository#findById以便返回您想要的实体。下面的示例使用的是RestAssuredMockMvc,但是如果您WebApplicationContext也注入 ,则可以使用 MockMvc 执行相同的操作。

@RunWith(SpringRunner.class)
@SpringBootTest(classes = SomeApplication.class)
public class SomeControllerTest {

    @Autowired
    private WebApplicationContext context;

    @MockBean(name = "mvcConversionService")
    private WebConversionService webConversionService;

    @Before
    public void setup() {
        RestAssuredMockMvc.webAppContextSetup(context);

        SomeEntity someEntity = new SomeEntity();

        when(webConversionService.canConvert(any(TypeDescriptor.class), any(TypeDescriptor.class)))
                .thenReturn(true);

        when(webConversionService.convert(eq("1"), any(TypeDescriptor.class), any(TypeDescriptor.class)))
                .thenReturn(someEntity);
    }
}
Run Code Online (Sandbox Code Playgroud)

在某些时候,Spring Boot 将执行WebConversionService::convertDomainClassConverter::convert稍后将调用类似的内容invoker.invokeFindById,它将使用实体存储库来查找实体。

那么为什么要嘲笑WebConversionService而不是呢DomainClassConverter?因为DomainClassConverter是在应用程序启动期间实例化而无需注入:

DomainClassConverter<FormattingConversionService> converter =
        new DomainClassConverter<>(conversionService);
Run Code Online (Sandbox Code Playgroud)

同时,WebConversionService是一个允许我们模拟它的 bean:

@Bean
@Override
public FormattingConversionService mvcConversionService() {
    WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat());
    addFormatters(conversionService);
    return conversionService;
}
Run Code Online (Sandbox Code Playgroud)

将模拟 bean 命名为 很重要mvcConversionService,否则它不会替换原始 bean。

关于存根,您需要模拟 2 个方法。首先你必须告诉你的模拟可以转换任何东西:

when(webConversionService.canConvert(any(TypeDescriptor.class), any(TypeDescriptor.class)))
        .thenReturn(true);
Run Code Online (Sandbox Code Playgroud)

然后是 main 方法,它将匹配 URL 路径中定义的所需实体 ID:

when(webConversionService.convert(eq("1"), any(TypeDescriptor.class), any(TypeDescriptor.class)))
        .thenReturn(someEntity);
Run Code Online (Sandbox Code Playgroud)

到目前为止,一切都很好。但也匹配目的地类型不是更好吗?就像是eq(TypeDescriptor.valueOf(SomeEntity.class))?可以,但是这会创建一个 TypeDescriptor 的新实例,当在域转换期间调用此存根时,该实例将不匹配。

这是我使用过的最干净的解决方案,但我知道如果 Spring 允许的话,效果可能会好得多。