如何干净地测试使用 DomainClassConverter 检索参数的 Spring Controller?
我非常喜欢干净且隔离良好的单元测试.但是我在这里的干净"部分绊倒了测试一个控制器,该控制器使用 DomainClassConverter
功能来获取实体作为其映射方法的参数.
I am big on clean well-isolated unit tests. But I am stumbling on the "clean" part here for testings a controller that uses DomainClassConverter
feature to get entities as parameters for its mapped methods.
@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
来查找实体.我想知道的是如何在测试中干净地模拟它.通过执行以下步骤,我取得了一些成功:
So from the DomainClassConverter
small documentation I know that it uses CrudRepository#findById
to find entities. What I would like to know is how can I mock that cleanly in a test.
I have had some success by doing this steps:
- 创建一个我可以模拟的自定义转换器/格式化程序
- 用上面的转换器实例化我自己的 MockMvc
- 在每次测试时重置模拟并更改行为.
问题在于设置代码很复杂,因此很难调试和解释(我的团队 99% 都是来自 rails 或 uni 的初级人员,所以我们必须保持简单).我想知道是否有办法从我的单元测试中注入所需的 MyEntity
实例,同时继续使用 @Autowired
MockMvc
进行测试.
The problem is that the setup code is complex and thus hard to debug and explain (my team is 99% junior guys coming from rails or uni so we have to keep things simple). I was wondering if there is a way to inject the desired MyEntity
instances from my unit test while keep on testing using the @Autowired
MockMvc
.
目前我正在尝试查看是否可以为 MyEntity
注入 CrudRepository
的模拟,但没有成功.我已经有几年没有在 Spring/Java 中工作了 (4),所以我对可用工具的了解可能不是最新的.
Currently I am trying to see if I can inject a mock of the CrudRepository
for MyEntity
but no success. I have not worked in Spring/Java in a few years (4) so my knowledge of the tools available might not be up to date.
推荐答案
所以从 DomainClassConverter 小文档中我知道它使用 CrudRepository#findById 来查找实体.我想知道的是如何在测试中干净利落地模拟它.
So from the DomainClassConverter small documentation I know that it uses CrudRepository#findById to find entities. What I would like to know is how can I mock that cleanly in a test.
您需要模拟在 CrudRepository#findById
之前调用的 2 个方法,以便返回您想要的实体.下面的示例使用 RestAssuredMockMvc
,但如果您也注入 WebApplicationContext
,您也可以使用 MockMvc 做同样的事情.
You will need to mock 2 methods that are called prior the CrudRepository#findById
in order to return the entity you want. The example below is using RestAssuredMockMvc
, but you can do the same thing with MockMvc if you inject the WebApplicationContext
as well.
@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
code>,它将使用实体存储库来查找实体.
At some point Spring Boot will execute the WebConversionService::convert
, which will later call DomainClassConverter::convert
and then something like invoker.invokeFindById
, which will use the entity repository to find the entity.
那么为什么要模拟 WebConversionService
而不是 DomainClassConverter
?因为DomainClassConverter
是在应用启动时实例化的,没有注入:
So why mock WebConversionService
instead of DomainClassConverter
? Because DomainClassConverter
is instantiated during application startup without injection:
DomainClassConverter<FormattingConversionService> converter =
new DomainClassConverter<>(conversionService);
同时,WebConversionService
是一个允许我们模拟它的 bean:
Meanwhile, WebConversionService
is a bean which will allow us to mock it:
@Bean
@Override
public FormattingConversionService mvcConversionService() {
WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat());
addFormatters(conversionService);
return conversionService;
}
将mock bean命名为mvcConversionService
很重要,否则它不会替换原来的bean.
It is important to name the mock bean as mvcConversionService
, otherwise it won't replace the original bean.
关于存根,您需要模拟 2 个方法.首先你必须告诉你你的模拟可以转换任何东西:
Regarding the stubs, you will need to mock 2 methods. First you must tell that your mock can convert anything:
when(webConversionService.canConvert(any(TypeDescriptor.class), any(TypeDescriptor.class)))
.thenReturn(true);
然后是main方法,它将匹配URL路径中定义的所需实体ID:
And then the main method, which will match the desired entity ID defined in the URL path:
when(webConversionService.convert(eq("1"), any(TypeDescriptor.class), any(TypeDescriptor.class)))
.thenReturn(someEntity);
到目前为止一切顺利.但是匹配目标类型不是更好吗?eq(TypeDescriptor.valueOf(SomeEntity.class))
之类的东西?会,但这会创建一个 TypeDescriptor 的新实例,在域转换期间调用此存根时,该实例将不匹配.
So far so good. But wouldn't be better to match the destination type as well? Something like eq(TypeDescriptor.valueOf(SomeEntity.class))
? It would, but this creates a new instance of a TypeDescriptor, which will not match when this stub is called during the domain conversion.
这是我投入工作的最干净的解决方案,但我知道如果 Spring 允许它可能会好很多.
This was the cleanest solution I've put to work, but I know that it could be a lot better if Spring would allow it.
相关文章