Spring Rest Docs with JUnit 5 and Webflux - java

I am unable to get a Spring Rest Docs test with JUnit 5 and Webflux working.
I have a working integration test with #WebFluxTest like this:
#WebFluxTest(SomeController.class)
class SomeControllerTest {
#Autowired
private WebTestClient testClient;
#MockBean
private SomeService service;
#Test
void testGetAllEndpoint() {
when(service.getAll())
.thenReturn(List.of(new Machine(1,"Machine 1", "192.168.1.5", 9060)));
testClient.get().uri("/api/machines")
.exchange()
.expectStatus().isOk()
.expectBodyList(Machine.class)
.hasSize(1);
}
}
I now want to write a documentation test. According to the docs, something like this should work:
#SpringBootTest
#ExtendWith({RestDocumentationExtension.class, SpringExtension.class})
class SomeControllerDocumentation {
private WebTestClient testClient;
#MockBean
private SomeService service;
#BeforeEach
public void setUp(WebApplicationContext webApplicationContext,
RestDocumentationContextProvider restDocumentation) {
this.testClient = WebTestClient.bindToApplicationContext(webApplicationContext)
.configureClient()
.filter(documentationConfiguration(restDocumentation))
.build();
}
#Test
void testGetAllEndpoint() {
when(service.getMachines())
.thenReturn(List.of(new Machine(1, "Machine 1", "192.168.1.5", 9060)));
testClient.get().uri("/api/machines")
.accept(MediaType.APPLICATION_JSON)
.exchange().expectStatus().isOk()
.expectBody().consumeWith(document("machines-list"));
}
}
I however get:
org.junit.jupiter.api.extension.ParameterResolutionException:
Failed to resolve parameter [org.springframework.web.context.WebApplicationContext webApplicationContext]
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'org.springframework.web.context.WebApplicationContext' available:
expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
I am also wondering if #SpringBootTest is needed as annotation or if #WebFluxTest(SomeController.class) is also supposed to work.
I am using Spring Boot 2.1.3 with spring-restdocs-webtestclient as dependency.

Instead of injecting WebApplicationContext use this:
#Autowired
private ApplicationContext context;
#BeforeEach
void setUp(RestDocumentationContextProvider restDocumentation) {
client = WebTestClient.bindToApplicationContext(context)
.configureClient()
.filter(documentationConfiguration(restDocumentation))
.build();
}

As an alternative to the answer of Gilad Peleg, you can just change the type in the method argument from WebApplicationContext to ApplicationContext.
Note also that instead of #SpringBootTest, you can use #WebFluxTest(SomeController.class)

According to docs:
#AutoConfigureRestDocs can also be used with WebTestClient. You can
inject it by using #Autowired and use it in your tests as you normally
would when using #WebFluxTest and Spring REST Docs, as shown in the
following example:
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;
#RunWith(SpringRunner.class)
#WebFluxTest
#AutoConfigureRestDocs
public class UsersDocumentationTests {
#Autowired
private WebTestClient webTestClient;
}

Related

Integration test for single Spring Boot #Service class

I'm writing an integration test for this Spring Boot #Service bean
import org.springframework.stereotype.Service;
import org.thymeleaf.ITemplateEngine;
import org.thymeleaf.context.Context;
import java.util.Locale;
import java.util.Map;
#Service
public class ThymeLeafEmailTemplateService implements EmailTemplateService {
private final ITemplateEngine springTemplateEngine;
public ThymeLeafEmailTemplateService(ITemplateEngine springTemplateEngine) {
this.springTemplateEngine = springTemplateEngine;
}
public String generateEmailBody(String template, Map<String, Object> variables) {
Context context = new Context(Locale.getDefault(), variables);
return springTemplateEngine.process(template, context);
}
}
Currently, the test class is defined as shown below
#SpringBootTest
class ThymeLeafEmailTemplateServiceTests {
#Autowired
private EmailTemplateService service;
#Test
void generateTaskNotificationEmail() {
var output = service.generateEmailBody("/template", Map.of());
assertEquals("Expected Output", output);
}
}
A problem with this approach is that it's very slow/inefficient because the entire application context is loaded, but I really only need the service being tested and its dependencies.
If I change the test class' annotations to
#SpringBootTest
#ContextConfiguration(classes = ThymeLeafEmailTemplateService.class)
the test fails, because the dependency ITemplateEngine springTemplateEngine does not exist. I could add this dependency to classes (the list of beans to create), but this seems like a very brittle approach.
Is there an efficient way to integration test a single #Service?
Note:
I know I could mock ITemplateEngine springTemplateEngine and write a unit test instead, but I want to test the template's actual output, so this approach won't work
You can use
#WebMvcTest(ThymeLeafEmailTemplateService.class)
This will load only that bean in your application context along with any default Spring configuration beans.

Springboot integration tests are failing when adding multiple service

I have a spring boot application with one controller class, one Service and one repository working perfectly fine. I have added Junit test cases for the same and that is also working perfectly fine.
#RestController
public class EmployeeController{
#Autowired
EmployeeService service;
#GetMapping("/list")
public List<Employee> getEmployee(){
return service.findAll();
}
}
#Service
public class EmployeeService{
#Autowired
EmployeeRepository repository;
public List<Employee> findAll(){
return repository.findAll();
}
}
#Repository
public interface EmployeeRepository extends JpaRepository<Employee, String>{}
The test class is below.
#RunWith(SpringRunner.class)
#WebMvcTest(value = EmployeeController.class)
class EmployeeControllerIntegrationTest {
#Autowired
private MockMvc mvc;
#MockBean
private EmployeeService service;
#Test
public void findAllEmployeeTest(){
}
}
The test case is passing until here, but at the moment I am adding another API as below all tests are failing.
#RestController
public class DepartmentController{
#Autowired
DepartmentService service;
#GetMapping("/list")
public List<Department> getDepartment(){
return service.findAll();
}
}
#Service
public class DepartmentService{
#Autowired
DepartmentRepository repository;
public List<Department> findAll(){
return repository.findAll();
}
}
#Repository
public interface DepartmentRepository extends JpaRepository<Department, String>{}
The test class is below.
#RunWith(SpringRunner.class)
#WebMvcTest(value = DepartmentController.class)
class DepartmentControllerIntegrationTest {
#Autowired
private MockMvc mvc;
#MockBean
private DepartmentService service;
#Test
public void findAllDepartmentTest(){
}
}
After adding the Department services test cases are failing with below error:
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'departmentController': Unsatisfied dependency expressed through field 'departmentService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'departmentService': Unsatisfied dependency expressed through field 'repository'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.employeeapp.data.repository.DepartmentRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
Cheers!
For what i see, you try to do an IT test by mocking your service. In IT test you don't mock your service. In Integration Test you test that all work together. If you don't mock the repository it's more an E2E Test (end to end you test all the interaction path).
You can mock the repository if there is no logic to test, and/or don't want to write in your database.
So if it's unit test you can try that (it work on my projects) and i use Junit 5 (org.junit.jupiter)
#WebMvcTest(DepartementController.class)
public class DepartementControllerTest {
#MockBean
private DepartmentService departmentService;
#Autowired
MockMvc mockMvc;
#Test
public void findAllTest(){
// You can create a list for the mock to return
when(departmentService.findAll()).thenReturn(anyList<>);
//check if the controller return something
assertNotNull(departmentController.findAll());
//you check if the controller call the service
Mockito.verify(departmentService, Mockito.times(1)).findAll(anyList());
}
That's a unit test.
An It test will be more like
#ExtendWith(SpringExtension.class)
#SpringBootTest
public class DepartmentIT {
#Autowired
DepartmentService departmentService;
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#BeforeEach
void setUp() {
this.mockMvc =
MockMvcBuilders.webAppContextSetup(this.wac)
.build();
}
#Test
void departmentFindAll() throws Exception {
MvcResult mvcResult = this.mockMvc.perform(get("/YOUR_CONTROLLER_URL")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().is(200))
.andReturn();
assertTrue(mvcResult.getResponse().getContentAsString().contains("SOMETHING_EXPECTED"));
assertTrue(mvcResult.getResponse().getContentAsString().contains("SOMETHING_EXPECTED"));
assertTrue(mvcResult.getResponse().getContentAsString().contains("SOMETHING_EXPECTED"));
//You also can check its not an empty list, no errors are thrown etc...
}
the imports use so you dont search too much are
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
No qualifying bean of type 'com.employeeapp.data.repository.DepartmentRepository' available: expected at least 1 bean which qualifies as autowire candidate.
Spring looks for the bean of TYPE: DepartmentRepository.
Define the Bean
#Repository
public interface DepartmentRepository extends JpaRepository<Department, String>{}
Add the annotation here #Repository so Spring can turn DepartmentRepository into a Bean.
Autowire the Correct Type and Name
#Service
public class DepartmentService{
#Autowired
private DepartmentRepository repository; // The best practice is to write the full bean name here departmentRepository.
public List<Department> findAll(){
return repository.findAll();
}
}

Unable to autowire RestTemplate for unit test

I have a service which uses an autowired instance of RestTemplate like below
#Service
class SomeAPIService {
private RestTemplate restTemplate;
SomeAPIService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
this.restTemplate.setRequestFactory(HttpUtils.getRequestFactory());
}
}
Everything runs fine in non-test environment. But when I try to run following unit test in test profile, it starts complaining about unable to autowire rest template.
#RunWith( SpringJUnit4ClassRunner.class )
#SpringBootTest(classes = MyApplication.class, webEnvironment = RANDOM_PORT, properties = "management.port:0")
#ActiveProfiles(profiles = "test")
#EmbeddedPostgresInstance(flywaySchema = "db/migration")
public abstract class BaseTest {
}
#SpringBootTest(classes = SomeAPIService.class)
public class SomeAPIServiceTest extends BaseTest {
#Autowired
SomeAPIService someAPIService;
#Test
public void querySomeAPI() throws Exception {
String expected = someAPIService.someMethod("someStringParam");
}
}
Following is the detailed exception -
Caused by:
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'someAPIService': Unsatisfied dependency
expressed through constructor parameter 0; nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
qualifying bean of type 'org.springframework.web.client.RestTemplate'
available: expected at least 1 bean which qualifies as autowire
candidate. Dependency annotations: {}
Any clues?
Following helped me get the correct dependencies autowired. The solution is to also include RestTemplate.class in list of classes given to SpringBootTest.
#SpringBootTest(classes = {RestTemplate.class, SomeAPIService.class})
class SomeAPIService {
#Autowired
SomeAPIService someAPIService;
#Test
public void querySomeAPI() throws Exception {
String expected = someAPIService.someMethod("someStringParam");
}
}
#Emre answer was helpful in guiding me towards the final solution.
You are trying to autowire SomeAPIService without satisfying its dependencies. You should inject Rest Template to SomeAPIService. But you are getting NoSuchBeanDefinitionException for Rest Template.
Take a look how to inject it :
How to autowire RestTemplate using annotations
Alternative answer would be - to use TestRestTemplate
From official docs >>>
TestRestTemplate can be instantiated directly in your integration tests, as shown in the following example:
public class MyTest {
private TestRestTemplate template = new TestRestTemplate();
#Test
public void testRequest() throws Exception {
HttpHeaders headers = this.template.getForEntity(
"https://myhost.example.com/example", String.class).getHeaders();
assertThat(headers.getLocation()).hasHost("other.example.com");
}
}
Alternatively, if you use the #SpringBootTest annotation with WebEnvironment.RANDOM_PORT or WebEnvironment.DEFINED_PORT, you can inject a fully configured TestRestTemplate and start using it. If necessary, additional customizations can be applied through the RestTemplateBuilder bean.

Context initialization issue for controller Unit testing with Spring 3.2 and Mockito

I'm trying to provide a clean Unit Test for a Controller of mine. This Controller has a Service as dependency and this Serviceh has a Datasource as dependency.
The test looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration
public class ContentActionWebServiceControllerTest {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Autowired
private MyService myService;
#Test
public void getRequestActionList() throws Exception {
when(...)
perform(...);
verify(...);
}
#Configuration
#ImportResource("...")
static class MyTestConfiguration {
#Bean
public MyService myService() {
return Mockito.mock(MyService.class);
}
}
}
And the MyService is something like
#Service
public class MyService {
#Autowired
private MyDataSource myDatasource;
...
}
Because MyService as an Autowired property MyDataSource, the context isn't initialized because it doesn't find any MyDataSource type for satisfying the #Autowired annotation of MyService. But why does it ever try to resolve this annotation? Is this is a mock?
Mockito does use cglib to create a new child class of MyService (and override all methods with mock methods).
But still, the dependencies of the parent will be injected, because this is how Spring does it's job:
if you have a parent class with some #Autowired fields, and a child class that inherits from this parent class, then Spring will inject the #Autowired fields of the parent when instantiating the child. I guess it's the same behavior in your case.
If you use an interface for MyService, then your problem will be solved.
If it's supposed to be a unit test (and not an integration test) you don't even need to use Spring, you can do it all with JUnit+Mockito. Rather than #Autowireing dependencies from Spring context, you can simply create mocks of the support objects (via #Mock) and inject them to the testee (via #InjectMocks). I believe your code could be simplified to something (conceptually) like this:
#RunWith(MockitoJUnitRunner.class)
public class ContentActionWebServiceControllerTest {
#Mock
private Service mockServiceUsedByController;
#InjectMocks
private YourController testee;
#Test
public void getRequestActionList() throws Exception {
assertFalse(testee.getRequestActionList().isEmpty());
// etc.
}
}

Injecting mock #Service for Spring unit tests

I am testing a class that uses use #Autowired to inject a service:
public class RuleIdValidator implements ConstraintValidator<ValidRuleId, String> {
#Autowired
private RuleStore ruleStore;
// Some other methods
}
But how can I mock ruleStore during testing? I can't figure out how to inject my mock RuleStore into Spring and into the Auto-wiring system.
Thanks
It is quite easy with Mockito:
#RunWith(MockitoJUnitRunner.class)
public class RuleIdValidatorTest {
#Mock
private RuleStore ruleStoreMock;
#InjectMocks
private RuleIdValidator ruleIdValidator;
#Test
public void someTest() {
when(ruleStoreMock.doSomething("arg")).thenReturn("result");
String actual = ruleIdValidator.doSomeThatDelegatesToRuleStore();
assertEquals("result", actual);
}
}
Read more about #InjectMocks in the Mockito javadoc or in a blog post that I wrote about the topic some time ago.
Available as of Mockito 1.8.3, enhanced in 1.9.0.
You can use something like Mockito to mock the rulestore returned during testing. This Stackoverflow post has a good example of doing this:
spring 3 autowiring and junit testing
You can do following:
package com.mycompany;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
#Component
#DependsOn("ruleStore")
public class RuleIdValidator implements ConstraintValidator<ValidRuleId, String> {
#Autowired
private RuleStore ruleStore;
// Some other methods
}
And your Spring Context should looks like:
<context:component-scan base-package="com.mycompany" />
<bean id="ruleStore" class="org.easymock.EasyMock" factory-method="createMock">
<constructor-arg index="0" value="com.mycompany.RuleStore"/>
</bean>

Categories