如何彻底测试使用DomainClassConverter检索参数的Spring控制器?

时间:2019-06-26 01:24:10

标签: java spring unit-testing spring-mvc mockito

我擅长进行干净,隔离良好的单元测试。但是我在这里的“干净”部分碰巧测试一个使用DomainClassConverter功能来获取实体作为其映射方法参数的控制器。

@Entity
class MyEntity {
    @Id
    private Integer id;
    // rest of properties goes here.
}

这样定义控制器

@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
    }
}

因此,从DomainClassConverterdocumentation开始,我知道它使用CrudRepository#findById查找实体。我想知道的是如何在测试中清晰地进行模拟。 通过执行以下步骤,我取得了一些成功:

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

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

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

1 个答案:

答案 0 :(得分:3)

  

因此从DomainClassConverter小型文档中,我知道它使用CrudRepository#findById查找实体。我想知道的是如何在测试中进行简洁的模拟。

您将需要模拟在CrudRepository#findById之前调用的2个方法,以便返回所需的实体。下面的示例使用的是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);
    }
}

Spring Boot将在某个时候执行WebConversionService::convert,随后将调用DomainClassConverter::convert,然后执行类似invoker.invokeFindById的操作,它将使用实体存储库来查找实体。

那为什么要模拟WebConversionService而不是DomainClassConverter?因为DomainClassConverter是在应用程序启动期间实例化的,所以没有注入:

DomainClassConverter<FormattingConversionService> converter =
        new DomainClassConverter<>(conversionService);

与此同时,WebConversionService是一个可以让我们模拟它的bean:

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

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

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

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

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

when(webConversionService.convert(eq("1"), any(TypeDescriptor.class), any(TypeDescriptor.class)))
        .thenReturn(someEntity);

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

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