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.
}
The controller is defined like this
@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
}
}
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:
- Create a custom Converter/Formatter that I can mock
- Instantiate my own MockMvc with above converter
- reset mock and change behaviour at each test.
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
.
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.
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 usingRestAssuredMockMvc
, but you can do the same thing with MockMvc if you inject theWebApplicationContext
as well.At some point Spring Boot will execute the
WebConversionService::convert
, which will later callDomainClassConverter::convert
and then something likeinvoker.invokeFindById
, which will use the entity repository to find the entity.So why mock
WebConversionService
instead ofDomainClassConverter
? BecauseDomainClassConverter
is instantiated during application startup without injection:Meanwhile,
WebConversionService
is a bean which will allow us to mock it:It is important to name the mock bean as
mvcConversionService
, otherwise it won't replace the original bean.Regarding the stubs, you will need to mock 2 methods. First you must tell that your mock can convert anything:
And then the main method, which will match the desired entity ID defined in the URL path:
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.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.