我擅长于干净隔离良好的单元测试。但是我在这里的“干净”部分碰巧测试了使用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
}
}
因此,从
DomainClassConverter
小documentation我知道它使用CrudRepository#findById
查找实体。我想知道的是如何在测试中清晰地进行模拟。通过执行以下步骤,我取得了一些成功:
创建一个我可以模拟的自定义转换器/格式化程序
使用上述转换器实例化我自己的MockMvc
重置模拟并在每次测试时更改行为。
问题在于设置代码很复杂,因此难以调试和解释(我的团队是来自Rails或uni的99%的初级人员,因此我们必须保持简单)。我想知道是否有办法从单元测试中注入所需的
MyEntity
实例,同时继续使用@Autowired
MockMvc
进行测试。目前,我正在尝试查看是否可以为
CrudRepository
注入MyEntity
的模拟,但是没有成功。几年来我一直没有在Spring / Java中工作(4),所以我对可用工具的了解可能不是最新的。 最佳答案
因此,从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允许的话,可能会更好。