JUnit 5 Unit Testing Get and Post methods - spring-boot

I am new to spring boot and Junit testing. I am facing problem in testing Rest Api's Get and Post method. Many tutorials online guide about the use of mockMvc in JUnit testing. But are those still valid for JUnit 5.
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = RegisterRestController.class)
class RegisterRestControllerTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#MockBean
private RegisterUseCase registerUseCase;
#Test
void whenValidInput_thenReturns200() throws Exception {
mockMvc.perform(...);
}
}
However I read a article that you don't need to include #ExtendWith extension from SpringBoot 2.1 onwards.
Since Junit 5 and spring boot both updated with lot of changes from previous version it has become difficult to analyse which code available online is correct regarding testing.
I am not able to use MockMvc.perform().andExpect(status().isOk()).
mockMvc.perform(post("/forums/{forumId}/register", 42L)
.contentType("application/json")
.param("sendWelcomeMail", "true")
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isOk());
What is the correct use of testing Get and post methods. And testing Rest verbs comes under unit testing or integration testing?
Also guide me good source to check updated features of Junit 5 with Spring Boot(JUnit 5 doc is not helpful in my case).

You can use the MockMvcRequestBuilders inside your unit test. Here is an example with post.
#ExtendWith(SpringExtension.class)
#WebMvcTest(RegisterRestController.class)
#AutoConfigureMockMvc(addFilters = false) //for disabling secruity, remove if not needed
class RegisterRestControllerTest {
#MockBean
private MyService myService;
#Autowired
private ObjectMapper objectMapper;
#Autowired
private MockMvc mockMvc;
#Test
void someTest() {
// mock post of new User
when(myService.createPojo(User.class)))
.thenAnswer((Answer<User>)
invocationOnMock -> invocationOnMock.getArgument(0, User.class));
// test post request with data
mockMvc.perform(MockMvcRequestBuilders
.post("/forums/{forumId}/register", 42L)
.contentType("application/json")
.param("sendWelcomeMail", "true")
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("some name"));
}
Similarly you can test GET, PUT and DELETE. Hope that helps you get into testing your controller.
To answer the unit vs. integration test part: In this case, you are testing a unit, the controller. The service, that handles the transaction and all other layers are mocked, eg with mockito in my example code.
For an integration test, you wouldn't use any mocks. And you would have a real database or at least a "fake one" like h2.

Related

NullPointerException while I'm trying to unit test Spring Boot Application

When I try to test my CRUD operations in my Spring Boot Applications, I get NullPointerException saying that this.Repository is null. What can I do to resolve this issue? Am I missing something?
My test class:
#RunWith(MockitoJUnitRunner.class)
class AppointmentServiceTest {
#Mock
private AppointmentRepository appointmentRepository;
#InjectMocks
private AppointmentService appointmentService;
#Test
void shouldGetAllAppointments() {
List<Appointment> appointments = new ArrayList<>();
appointments.add(new Appointment());
given(appointmentRepository.findAll()).willReturn(appointments);
List<Appointment> expectedAppointments = appointmentService.getAllAppointments();
assertEquals(expectedAppointments, appointments);
verify(appointmentRepository.findAll());
}
}
I am getting NullPointerException:
java.lang.NullPointerException: Cannot invoke "com.app.hospitalmanagementsystem.repository.AppointmentRepository.findAll()" because "this.appointmentRepository" is null
Since the spring boot is tagged here, the chances that you're using spring boot 2.x (1.x is outdated these days)
But if so, you should be running JUnit 5 tests (spring boot 2.x works with Junit 5)
So instead of #RunWith annotation, use #ExtendsWith
Then place the breakpoint in the test and make sure that mockito extension has actually worked and the mock is created.
Now as for the given - I can't say for sure, I haven't used this syntax (BDD Mockito), but in a "clean mockito" it should be Mockito.when(..).thenReturn
All-in-all try this code:
#ExtendsWith(MockitoExtension.class)
class AppointmentServiceTest {
#Mock
private AppointmentRepository appointmentRepository;
#InjectMocks
private AppointmentService appointmentService;
#Test
void shouldGetAllAppointments() {
List<Appointment> appointments = new ArrayList<>();
appointments.add(new Appointment());
Mockito.when(appointmentRepository.findAll()).thenReturn(appointments);
List<Appointment> expectedAppointments = appointmentService.getAllAppointments();
assertEquals(expectedAppointments, appointments);
verify(appointmentRepository.findAll());
}
}

Springboot test controller via WebClient instead of WebTestClient

I'm attempting something perhaps misguided but please help.
I would like to test springboot controller via #WebFluxTest. However I would like to use WebClient instead of WebTestClient. How can this be done?
So far, I managed to use reflection to get ExchangeFunction out of WebTestClient and assign it to WebClient - and it works! Calls are made, controller responds - wonderful. However I don't think this is good approach. Is there a better way?
Thank you.
Ideally, you should use a WebTestClient which is more of a convenience wrapper around the WebClient. Just like the TestRestTemplate is for a RestTemplate. Both allow a request to be created and you can easily make assertions. They exist to make your test life easier.
If you really want to use a WebClient instead of a WebTestClient and do the assertions manually (which means you are probably complicating things) you can use the WebClient.Builder to create one. Spring Boot will automatically configure one and you can simply autowire it in your test and call the build method.
#SpringBootTest
public void YourTest {
#Autowired
private WebClient.Builder webClientBuilder;
#Test
public void doTest() {
WebClient webClient = webClientBuilder.build();
}
}
The same should work with #WebFluxTest as well.
Ok, after much experimentation here is a solution to test springboot controller & filters via a mocked connection - no webservice, no ports and quick test.
Unfortunately I didn't work out how to do it via #WebFluxTest and WebClient, instead MockMvc can be used to achieve desired result:
#ExtendWith(SpringExtension.class)
#Import({SomeDependencyService.class, SomeFilter.class})
#WebMvcTest(controllers = SomeController.class, excludeAutoConfiguration = SecurityAutoConfiguration.class)
#AutoConfigureMockMvc()
public class SomeControllerTest {
#MockBean
private SomeDependencyService someDependencyService;
#Autowired
private MockMvc mockMvc;
private SomeCustomizedClient subject;
#BeforeEach
public void setUp() {
subject = buildClient();
WebClient webClient = mockClientConnection();
subject.setWebClient(webClient);
}
private WebClient mockClientConnection() {
MockMvcHttpConnector mockMvcHttpConnector = new MockMvcHttpConnector(mockMvc);
WebClient webClient = WebClient.builder().clientConnector(mockMvcHttpConnector).build();
return webClient;
}
#Test
public void sample() {
when(SomeDependencyService.somePersistentOperation(any(), any())).thenReturn(new someDummyData());
SomeDeserializedObject actual = subject.someCallToControllerEndpoint("example param");
assertThat(actual.getData).isEquals("expected data");
}
}
Now it is possible to test your customized client (for example if you have internal java client that contains few important customization like security, etags, logging, de-serialization and uniform error handling) and associated controller (and filters if you #import them along) at the cost of a unit test.
You do NOT have to bring up entire service to verify the client and controller is working correctly.

Using MockRestServiceServer only in subset of tests

I want to test outgoing HTTP calls from my service using MockRestServiceServer. I got it working using following code:
#SpringBootTest
class CommentFetcherTest {
#Autowired
RestTemplate restTemplate;
#Autowired
CommentFetcher commentFetcher;
#Test
public void shouldCallExternalService(){
MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
mockServer.expect(ExpectedCount.once(), requestTo("/test/endpoint")).andExpect(method(HttpMethod.GET));
//when
commentFetcher.fetchData();
mockServer.verify();
}
}
However the problem I run into is that RestTemplate is a bean shared between all tests from suite what makes impossible to further run integration tests which should be calling external services. This results in :
java.lang.AssertionError: No further requests expected
How is it possible to use MockRestServiceServer only for subset of tests?
I can't get rid of Dependency Injection through #Autowired and construct my service in tests such as
RestTemplate restTemplate = new RestTemplate();
CommentFetcher commentFetcher = new CommentFetcher(restTemplate);
As this would not read properties from application.properties necessary to proper functioning of my service.
Spring Boot provides a convenient way to test your client classes that make use of Spring's RestTemplate using #RestClientTest.
With this annotation, you get a Spring Test Context that only contains relevant beans and not your whole application context (which is what you currently get with #SpringBootTest).
The MockRestServiceServer is also part of this context and the RestTemplate is configured to target it.
A basic test setup can look like the following:
#RestClientTest(CommentFetcher.class)
class UserClientTest {
#Autowired
private CommentFetcher userClient;
#Autowired
private MockRestServiceServer mockRestServiceServer;
// ...
}
What's left is to stub the HTTP method call and then invoke the method on your class under test (commentFetcher.fetchData(); in your case).
Refer to this article if you want to understand more about how #RestClientTest can be used to test your RestTemplate usage.

How to get Hibernate Envers with Spring integration testing?

Is there some way to get Hibernate Envers to run immediately on the call to save or update instead of going at the end of the transaction ?
I have a #Transactional annotation on my (effectively end-to-end test). Since the code doesn't contain test methods I'm unable to put "flush" or whatever in it, (short of editing non-test code, which is not the desire).
I'm not sure how to get Envers log working on the calls. I need it to work as I'm using the Envers audit tables to check old entries at runtime, and would like to create test cases for this functionality.
I have the following code for unit testing :
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration("/src/webapp")
#EnableWebMvc
#Transactional
#TestPropertySource(locations= {"classpath:/config/common.properties"}, properties = {"..."})
#ContextConfiguration(locations = {"..."})
public class Test {
#Autowired
private WebApplicationContext wac;
#Autowired
private UserDetailsService detailsService;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.build();
}
...
}

How to dynamically load different class for JUnit tests?

I've written a couple of JUnit tests to test my REST functionality. Since I only want to test REST (and not the database, domain logic, ..) I made a stub filles with dummy data which stands for the rest of the backend.
[EDIT] For example I want to test /customers/all
A GET request will be responded to with an arraylist containing all names.
I therefore use MockMV.
this.mockMvc.perform(get("/customers/all").accept("application/json"))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isNotEmpty())
.andExpect(jsonPath("$[0].name", is("John")));
When you normally perform a GET request towards /customers/all a request will be sent to the database. Now, to test my REST controller I made a stub which responds to GET /customers/all with a simple arraylist containing just my name (as you can see in the test). When I test this local I simply replace the real class with this stub. How is this done dynamically?
I don't think your approach is the good one. Just use your real controller, but stub its dependencies (using Mockito, for example), just like you would do for a traditional unit test.
Once you have an instance of the controller using stubbed dependencies, you can use a standalone setup and use MockMvc to test, in addition to the controller code, the mapping annotations, the JSON marshalling, etc.
Thias approach is described in the documentation.
Example using Mockito, assuming the controller delegates to a CustomerService:
public class CustomerControllerTest {
#Mock
private CustomerService mockCustomerService;
#InjectMocks
private CustomerController controller;
private MockMvc mockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void shouldListCustomers() {
when(mockCustomerService.list()).thenReturn(
Arrays.asList(new Customer("John"),
new Customer("Alice")));
this.mockMvc.perform(get("/customers").accept("application/json"))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isNotEmpty())
.andExpect(jsonPath("$[0].name", is("John")));
}
}

Resources