spring-test MockMvc kotlin DSL lacks async support? - spring

spring-test has added support for a MockMvc DSL which can be found documented here:
https://docs.spring.io/spring/docs/5.2.0.M1/spring-framework-reference/languages.html#mockmvc-dsl
When testing a controller that returns a CompletableFuture (or any other async result type) a test using MockMvc needs to perform an asyncDispatch of the MvcResult before the body can be asserted.
This can be found on various blogs or stackoverflow questions:
https://sdqali.in/blog/2015/11/24/testing-async-responses-using-mockmvc/
MockMVC perform post test to async service
The new DSL seems to lack a clean way to do this.
For example the following code is needed to do the asyncDispatch:
#Test
internal fun call() {
val mvcResult = mockMvc.get("/execute") {
accept = APPLICATION_JSON
}.andExpect {
request { asyncStarted() }
}.andReturn()
mockMvc.perform(asyncDispatch(mvcResult))
.andExpect(MockMvcResultMatchers.status().isOk)
.andExpect(MockMvcResultMatchers.jsonPath("$.value", Is.`is`("test")))
}
Am I missing something that would enable this or is this just not nicely supported in the DSL (yet)?
Update:
I tried improving this with an extension function on ResultActionsDsl.
fun ResultActionsDsl.asyncDispatch(mockMvc: MockMvc):ResultActionsDsl {
val mvcResult = andReturn()
mockMvc.perform(MockMvcRequestBuilders.asyncDispatch(mvcResult))
return this
}
This makes it possible to write the test as:
#Test
internal fun call() {
mockMvc.get("/execute") {
accept = APPLICATION_JSON
}.andExpect {
request {
asyncStarted()
}
}
.asyncDispatch(mockMvc)
.andExpect {
status { isOk }
jsonPath("$.value") { value("test") }
}
}
I still feel like this would be supported out of the box by the DSL.

This will be supported as of Spring Framework 5.2.2 and Spring Boot 2.2.2 with the following syntax:
mockMvc.get("/async").asyncDispatch().andExpect {
status { isOk }
}
See related issue for more details.

Related

JUnit tests in Spring Boot 3.0 does not work

Code
GET method inside controller class:
#GetMapping("/getAllRecommendedMovies")
public ResponseEntity<BookMyTicket> getAllRecommendedMovies(
#RequestParam(value = "theatreName", required = false) String theatreName,
#RequestParam(value = "pincode", required = false) Integer pincode,
HttpServletRequest request) {
return Observation.createNotStarted(
request.getRequestURI().substring(1),
observationRegistry
).observe(() -> new ResponseEntity<(
theatreManagementService.getAllRecommendedMovies(theatreName, pincode),
HttpStatus.OK
));
}
JUnit test:
#Test
public void getAllRecommendedMovies() throws Exception {
try (MockedStatic<Observation> utilities = Mockito.mockStatic(Observation.class)) {
utilities.when(
() -> Observation.createNotStarted(Mockito.eq("getAllRecommendedMovies"), Mockito.any())
).thenReturn(Observation.NOOP);
}
mockMvc.perform(get("/getAllRecommendedMovies")).andExpect(status().isOk());
}
Also on Github: TheatreManagementControllerTest.java
Question
I have implemented JUnit test for ObservationRegistry.
Is there any alternate method to implement?
Mockito.mockStatic can only mock static calls that happen in the same thread. See https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#static_mocks.
Spring MVC tests run the Spring application in its own thread so static mocking with Mockito won't help here.
I suggest you introduce a ObservationService interface to wrap methods like Observation.createNotStarted(..) and use that service in your controller. The service can then be easily mocked using standard Spring testing mechanisms like #MockBean.

FluentValidations in Test project redirects to the main API project because of WebApplicationFactory

I was using WebApplicationFactory for integration tests, but then I wanted to use DI for my test classes for my services and repos and Xunit was not a big fan of interfaces and ctors, so I wanted to put all my services and dependencies in WebApplicationFactory which I think is the appropriate way but the thing is my main API project is a fully functioning API with auth (such as MSAL and branches, users that require internet connection). So, every time I call a validator I get 401
public class SqliteTests : IClassFixture<ApiWebAppFactory>
{
private readonly IValidator<Contact> _validator;
public SqliteTests(ApiWebAppFactory factory)
{
var scope = factory.Services.GetRequiredService<IServiceScopeFactory>().CreateScope();
//401 unauthorized here
_validator = scope.ServiceProvider.GetRequiredService<IValidator<Contact>>();
}
[Fact]
public async void MyTest()
{
//...
}
}
I usually fix this kind of problem by returning new objects from the IServiceProvider's ctor
like this:
public class ApiWebAppFactory :
WebApplicationFactory<Actual.API.Mappings.MappingProfiles>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
//...
services.AddScoped<IRepository<Contact>, Repository<Contact>>
(x =>
{
return new Repository<Contact>(x.GetRequiredService<SqliteMockDbContext>());
});
//...
But I couldn't find a way to do the same thing with the FluentValidation; validation and ValidatorFactory(some of our services use IValidatorFactory).
They always seem to call to the main API project's Program.cs and its all dependencies which ends up in 401 Unauthorized.
This code might look ugly but I also have the same issue with my IService which expects an IValidatorFactory;
services.AddScoped<IService<Contact, IRepository<Contact>,
BaseResponse<Contact>, BaseResponseRange<IEnumerable<BaseResponse<Contact>>,
Contact>>, Service<Contact, IRepository<Contact>, BaseResponse<Contact>,
BaseResponseRange<IEnumerable<BaseResponse<Contact>>, Contact>>>(
x =>
{
var repo = x.GetRequiredService<IRepository<Contact>>();
var uow = x.GetRequiredService<IUnitOfWork>();
return new Service<Contact, IRepository<Contact>, BaseResponse<Contact>,
BaseResponseRange<IEnumerable<BaseResponse<Contact>>, Contact>>(
repo,uow, //this = new ServiceProviderValidatorFactory(x)
);
}

Writing JUnit tests for restTemplate.delete with Observable throwing OnErrorNotImplementedException

I have two methods in my Spring Boot(version 1.3.3) project as follows;
public Observable<Void> methodA() {
return this.methodB().map(s -> {
methodC.subscribe( aVoid ->
// some code
);
// some code
});
}
private Observable<Void> methodC() {
return Observable<~>create(sub -> {
restTemplate.delete(//some code);
sub.onNext(null);
sub.onCompleted();
}).doOnNext(//some code)
.doOnError(//some code);
}
I am using rxjava version 1.3.8.
I am trying to write Junit tests for methodC where restTemplate.delete(//some code); throws a HttpClientErrorException. For that, mocked the restTemplate.delete(//some code); as follows;
#Test
public void test() {
// some code
Mockito.doThrow(new HttpClientErrorException(HttpStatus.NOT_FOUND)).when(restTemplate)
.delete(//some code);
// some code
final Observable<Void> result = methodA();
final TestSubscriber<Void> subscriber = new TestSubscriber();
result.subscribe(subscriber);
subscriber.assertNotCompleted();
subscriber.assertError(HttpClientErrorException.class);
}
The mock is working as expected but it throws rx.exceptions.OnErrorNotImplementedException: 404 NOT_FOUND and fails the test.
but when I run the code, it is working as expected.
What am I missing here? How can I mock restTemplate.delete(// some code); to throw HttpClientErrorException and assert it?
Thanks in advance!

SpringBoot testing with spring-retry

I had the following function (the function is not really important):
fun myRandomFunc(something: String?): List<Int> {
return listOf(5)
}
And you can imagine it was doing some API calls, returning list of some objects, etc. I could easily mock this function in test like this:
doReturn(
listOf(
5
)
)
.whenever(...).myRandomFunc("something")
But after I introduced (retry/recover) in the mix, that mock is now throwing
org.mockito.exceptions.misusing.NotAMockException at .... Any idea why?
This is the code with spring retry:
#Retryable(
value = [ApiException::class], maxAttempts = MAX_RETRIES,
backoff = Backoff(delay = RETRY_DELAY, multiplier = RETRY_MULTIPLIER, random = true)
)
fun myRandomFunc(something: String?): List<Int> {
return listOf(5)
}
#Recover
fun testMyRandomFunc(exception: Exception): List<Int> {
log.error("Exception occurred ...", exception)
throw RemoteServiceNotAvailableException("Call failed after $MAX_RETRIES retries")
}
The code works, it's functional, just the mocking of tests is now broken. Would appreciate some help
Spring retry creates a proxy around the object.
If there is an interface, the proxy is a JDK proxy; if not, CGLIB is used.
Mockito can't mock CGLIB (final) methods.

How to remove the "_embedded" property in Spring HATEOAS

I'm using Spring Boot and HATEOAS to build a REST API and when my API returns a collection, it is wrapped inside a "_embedded" property, like so:
{
"_links":{
"self":{
"href":"http://localhost:8080/technologies"
}
},
"_embedded":{
"technologies":[
{
"id":1,
"description":"A",
"_links":{
"self":{
"href":"http://localhost:8080/technologies/1"
}
}
},
{
"id":2,
"description":"B",
"_links":{
"self":{
"href":"http://localhost:8080/technologies/2"
}
}
}
]
}
}
I want the response to be like this:
{
"_links":{
"self":{
"href":"http://localhost:8080/technologies"
}
},
"technologies":[
{
"id":1,
"description":"A",
"_links":{
"self":{
"href":"http://localhost:8080/technologies/1"
}
}
},
{
"id":2,
"description":"B",
"_links":{
"self":{
"href":"http://localhost:8080/technologies/2"
}
}
}
]
}
My TechnologiesController:
#RestController
#ExposesResourceFor(Technology.class)
#RequestMapping(value = "/technologies")
public class TechnologiesController {
...
#ResquestMapping(method = RequestMethod.GET, produces = "application/vnd.xpto-technologies.text+json")
public Resources<Resource<Technology>> getAllTechnologies() {
List<Technology> technologies = technologyGateway.getAllTechnologies();
Resources<<Resource<Technology>> resources = new Resources<Resource<Technology>>(technologyResourceAssembler.toResources(technologies));
resources.add(linkTo(methodOn(TechnologiesController.class).getAllTechnologies()).withSelfRel());
return resources;
}
The configuration class has the annotation #EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL).
What is the best way to produce the response without the "_embedded"?
As the documentation says
application/hal+json responses should be sent to requests that accept
application/json
In order to omit _embedded in you response you'll need to add
spring.hateoas.use-hal-as-default-json-media-type=false
to application.properties.
I close HAL feature, because it is hard to using Resources/Resource by restTemplate. I disable this feature by following code:
public class SpringRestConfiguration implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setDefaultMediaType(MediaType.APPLICATION_JSON);
config.useHalAsDefaultJsonMediaType(false);
}
}
It work for me. HAL is good if there are more support with restTemplate.
Adding this Accept header to the request:
Accept : application/x-spring-data-verbose+json
For those who use Spring Data, and consider it as a problem - solution is to set
spring.data.rest.defaultMediaType = application/json
in application properties.
There still links will be available, but no _embedded any more.
What you're describing in the produced and expected results are semantically different things. The former thing is the HAL representation of a Collection<Technology>. The latter, which you expect is the representation of:
class Wrapper {
Resources<Technology> technologies;
}
Note how this is how we actually create the top level technologies property that you would like to see in your response. You don't create any of the latter in your controller. A top-level Resourcesinstance is basically a collection and the only way to represent a top-level collection in HAL is _embedded. Apparently you don't want that but that's what you have written in your controller method.
Assuming you have Wrapper, something like this should work (untested):
Wrapper wrapper = new Wrapper(assembler.toCollectionModel(technologies);
EntityModel<Wrapper> model = EntityModel.of(wrapper);
model.add(linkTo(…));
PS: As of Spring HATEOAS 1.0, Resources is CollectionModel and Resourceis EntityModel.
You can use this code in the service
constructor(
private httpClient: HttpClient
) { }
retrieveAllStudents(){
return this.httpClient.get<any[]>(`http://localhost:8080/students`);
}
This will deal with the _embedded part of Json and extract the desired data.
export class ListStudentsComponent implements OnInit {
// declaring variables to be used
student: Student;
students: Student[];
message: string;
// injecting student service into the constuctor
constructor(
private studentService: StudentService,
) { }
ngOnInit() {
this.refreshStudents();
}
refreshStudents(){
this.studentService.retrieveAllStudents().subscribe(
response => {
console.log(response);
this.students = response._embedded.students as Student[];
}
);
}
For latest versions in Spring RepositoryRestConfigurer doesn't include the method public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) you'd need to override the default method on RepositoryRestConfigurer which include cors parameter.
public class RestConfiguration implements RepositoryRestConfigurer {
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
config.setDefaultMediaType(MediaType.APPLICATION_JSON);
config.useHalAsDefaultJsonMediaType(false);
}
}

Resources