I am trying to execute a JUnit test restful with spring boot application but it is wrong. But if I use a browser it's OK!
Help.
The Repository class:
package com.zhx.help.dao;
import com.zhx.help.model.Girl;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import javax.transaction.Transactional;
import java.util.List;
#Repository
#Transactional
public interface GirlRepository extends JpaRepository<Girl,Integer> {
List<Girl> findByAge(Integer age);
}
Controller class
package com.zhx.help.controller;
import com.zhx.help.dao.GirlRepository;
import com.zhx.help.model.Girl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
#RestController
public class GirlController {
#Autowired
private GirlRepository girlRepository;
/**查询所有女生列表*/
#GetMapping(value = "/girls")
public List<Girl> girlList(){
return girlRepository.findAll();
}
The JUnit:
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class GirlControllerTest extends MockMvcResultHandlers {
private static Logger log = LoggerFactory.getLogger(GirlControllerTest.class);
//模拟对象
private MockMvc mvc;
#MockBean
private GirlRepository girlRepository;
#Before
public void setUp() {
mvc = MockMvcBuilders.standaloneSetup(new GirlController()).build();
}
#Test
public void girlList() throws Exception {
RequestBuilder request = MockMvcRequestBuilders.get("/girls");
mvc.perform(request).andExpect(status().isOk());
}
The code
https://github.com/longfeizheng/springboot-oracle.git
With your current approach you are basically rendering #SpringBootTest useless. You are loading everything and the first thing you do in the setup is basically discard all the results.
Either use the started context by autowiring the MockMvc or create a simple unit test using Mockito.
Using a preconfigured MockMVC
By simply adding #Autowired on your MockMvc field you should be able to get the prepared instance and you can simply remove your setup method.
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class GirlControllerTest {
private static Logger log = LoggerFactory.getLogger(GirlControllerTest.class);
//模拟对象
#Autowired
private MockMvc mvc;
#MockBean
private GirlRepository girlRepository;
#Test
public void girlList() throws Exception {
RequestBuilder request = MockMvcRequestBuilders.get("/girls");
mvc.perform(request).andExpect(status().isOk());
}
Create a simple Unit test instead of Integration test
Another option is to not load the whole context and simply directly use Mockito and the standaloneSetup you have now.
#RunWith(MockitoJUnitRunner.class)
public class GirlControllerTest {
private static Logger log = LoggerFactory.getLogger(GirlControllerTest.class);
//模拟对象
private MockMvc mvc;
#Mock
private GirlRepository girlRepository;
#InjectMocks
private GirlController girlController;
#Before
public void setUp() {
mvc = MockMvcBuilders.standaloneSetup(girlController).build();
}
#Test
public void girlList() throws Exception {
RequestBuilder request = MockMvcRequestBuilders.get("/girls");
mvc.perform(request).andExpect(status().isOk());
}
Either way will work and which you need/want depends on your needs and what you want (simply test the controller or make it a huge integration test).
Note: You are extending MockMvcResultHandlers don't extend that class just use static imports instead.
You should not create a controller object using new ..
You can do something like below:
#InjectMocks
private GirlController girlController;
mvc = MockMvcBuilders.standaloneSetup(girlController).build();
You can avoid mocking a repository class. Instead create a service class and mock it and access Repository class inside the service class
Related
I am trying to read a value from a properties file for a unit test case in Spring Boot. I have two config.properties files, one in src/main/resources:
prop = some-value
and one in src/test/resources:
prop = some-test-value
Main Application class:
package company.division.project;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.PropertySource;
#SpringBootApplication(scanBasePackages = "company.division.project")
#PropertySource(value = "classpath:config.properties")
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
System.setProperty("DUMMY_PROPERTY", "dummy-value");
return application.sources(Application.class);
}
public static void main(String[] args) throws Exception {
// Do nothing with main
}
}
Service class to be tested:
package company.division.project.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
#Component
public class Service {
#Autowired
Environment environment;
public String getProperty() {
return environment.getProperty("prop");
}
}
ServiceTest class. I have tried two approaches to retrieving the value in the src/test/resources/config.properties file; one with an #Autowired Environment, and one with an #Value annotation...neither worked:
package company.division.project.service;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.test.context.TestPropertySource;
#RunWith(MockitoJUnitRunner.class)
#TestPropertySource("classpath:config.properties")
public class ServiceTest {
#InjectMocks
Service service;
#Autowired
Environment environment;
#Value("${prop}")
private String expectedProperty;
#Test
public void testGetPropertyWithValueAnnotation() {
assertEquals(expectedProperty, service.getProperty());
}
#Test
public void testGetPropertyWithEnvironment() {
assertEquals(environment.getProperty("prop"), service.getProperty());
}
}
I read somewhere on StackOverflow, that in order to auto-wire components in a Spring test class, I'll need to create an entire context for the test, so I tried this (change the annotations and test runner):
package company.division.project.service;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#SpringBootTest
public class ServiceTest {
#InjectMocks
Service service;
#Autowired
Environment environment;
#Value("${prop}")
private String expectedProperty;
#Test
public void testGetPropertyWithValueAnnotation() {
assertEquals(expectedProperty, service.getProperty());
}
#Test
public void testGetPropertyWithEnvironment() {
assertEquals(environment.getProperty("prop"), service.getProperty());
}
}
The context was created, but both approaches ended in NullPointerExceptions once again.
The problem with your test is that you are trying to use to MockitoJUnitRunner.class in a wrong way.
If you are mocking a Service using #InjectMocks you need to make sure you need to return the value Service.getProperty() by mocking the service call. If you are using SpringRunner.class then you shouldn't have #InjectMocks but should have #Autowired for the service. Following test works.
#RunWith(SpringRunner.class)
#SpringBootTest
public class ServiceTest {
#Autowired
Service service;
#Autowired
Environment environment;
#Value("${prop}")
private String expectedProperty;
#Test
public void testGetPropertyWithValueAnnotation() {
assertEquals(expectedProperty, service.getProperty());
}
#Test
public void testGetPropertyWithEnvironment() {
assertEquals(environment.getProperty("prop"), service.getProperty());
}
}
Thanks to #shazin's answer and some of my own research I've been able to solve the problem.
Basically, there needs to be compatibility between the test runner class specified in #RunWith and the annotations for the Mockito mocks. We want to test the Service class:
Service Class:
#Component
public class Service {
#Autowired
Environment environment;
public String getProperty() {
return environment.getProperty("prop");
}
}
If you're using #RunWith(MockitoJUnitRunner.class), you can use the #InjectMocks and #Mock annotations like below. Whatever is #Autowired in Service will be auto-wired with the mocks:
Test Class with MockitoJUnitRunner:
#RunWith(MockitoJUnitRunner.class)
public class ServiceTest {
#InjectMocks
Service service;
#Mock
Environment mockEnvironment;
#Before
public void before() {
Mockito.when(mockEnvironment.getProperty("prop")).thenReturn("some-test-value")
}
}
But you can't auto-wire anything in the test class itself. That requires a Spring Context (a Spring Context is needed to manage the beans which get auto-wired into objects). That's where #RunWith(SpringRunner.class) comes into the picture. You can use it to run a test case with a dedicated Spring context (you'll notice the test case logs showing a new Spring application being booted up for every test class with the #RunWith(SpringRunner.class) annotation). You'll also need to provide the Configuration details with the #SpringBootTest annotation.
The caveat is that a test class with #RunWith(SpringRunner.class) won't understand the #InjectMocks and #Mock annotations; you'll have to use the #MockBean annotation. This will effectively modify the Spring context by replacing beans with their mocks; anything with the #Autowired annotation will get auto-wired with the mock beans automatically:
Test Class with SpringRunner:
#RunWith(SpringRunner.class)
#SpringBootTest(classes=Application.class)
public class ServiceTest {
#Autowired
Service service;
#MockBean
Environment mockEnvironment;
#Before
public void before() {
Mockito.when(mockEnvironment.getProperty("prop")).thenReturn("some-test-value")
}
}
So...using the #RunWith(SpringRunner.class) didn't achieve anything except change the names of the annotations (#InjectMocks -> #Autowired, and #Mock -> #MockBean), right? Wrong. Using SpringRunner gives you the power of auto-wiring components within your test case. So if you want to use an actual Environment (not a mock one), you can do that as well; just auto-wire it in from the dedicated Spring context:
Test Class with SpringRunner and #Autowired Environment:
#RunWith(SpringRunner.class)
#SpringBootTest(classes=Application.class)
public class ServiceTest {
#Autowired
Service service;
#Autowired
Environment environment;
#Test
public void testServiceGetProperty() {
assertEquals(environment.getProperty("prop"), service.getProperty("prop");
}
}
And that solves the problem.
I wanted to do integration testing of my API.
#RestController
#RequestMapping("/api/v1")
public class TestController {
#Autowired
TestService testService;
#RequestMapping("/welcome")
public String welcomeMessage(#RequestParam("name") String name) {
return testService.welcomeMessage(name);
}
}
Below are the service interface and its implementation:
public interface TestService {
public String welcomeMessage(String name);
}
public class TestServiceImpl implements TestService{
#Autowired
TestRepository repo;
#Override
public String welcomeMessage(String name) {
repo.save(new StringEntity(name));
return "Hello "+name;
}
}
Below is the Test Case:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class MockitoTestingApplicationTests {
#Autowired
MockMvc mvc;
#MockBean
TestService testService;
#MockBean
TestController testController;
#Test
public void contextLoads() throws Exception {
Mockito.when(testController.welcomeMessage(ArgumentMatchers.anyString())).thenCallRealMethod();
Mockito.when(testService.welcomeMessage(ArgumentMatchers.anyString())).thenCallRealMethod();
mvc.perform(get("/api/v1/welcome").param("name", "dude")).andExpect(status().isOk());
}
}
I have a few questions.
when I'm executing the above code it is throwing an error saying cannot call real method on abstract methods. And When I'm mocking the TestServiceImpl, It is throwing NullPointerException in the Controller because the TestService is null. How should I fix that?
How should I mock the repository layer when we are using MongoDB. when I try to Mock MongoTemplate, It is throwing an error saying MongoConvertor must not be null
Is this the right way to write test cases. can we have code coverage without using thenCallRealMethod()?
Please suggest me how to proceed. Thanks in advance.
Make sure you have an implementation of the service i.e. TestServiceImpl annotated with #Service (or #Component if it is not strictly a service) and use spying instead of mocking:
#SpyBean
TestService testService;
Spying by default call real methods so you have to mock these that implementation you do not want to call.
Regarding repositories, you should mock the components annotated with #Repository, not the actual SessionFactory / Template etc. that are used within.
I am writing to write a unit test for my RestController (POST) and I am getting a NullPointerException on mvc.perform(...) line.
Here's my RestController :
#RestController
#EnableAutoConfiguration
public class MyController {
#Autowired
private Service1 service;
#Autowired
RestTemplate restTemplate;
#RequestMapping(value = "/logError", method = RequestMethod.POST, produces = {MediaType.APPLICATION_JSON_VALUE})
#ResponseBody
public ResponseEntity ErrorHandlor(#RequestBody JSONStructure jsonStructure) throws Exception{
service.getDocument(jsonStructure.getID(), jsonStructure.getLog());
return new ResponseEntity(HttpStatus.OK);
}
}
And here is my test class:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {MyController.class,
Service1.class, AppConfig.class})
#WebMvcTest(MyController.class)
public class MyControllerTest {
private MockMvc mockMvc;
#MockBean
private RestTemplate restTemplate;
MyController service = new MyController();
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(service).build();
}
#Test
public void testController() throws Exception{
ObjectMapper mapper = new ObjectMapper();
String url = "http://localhost:8080/logError";
JSONStructure structure = new JSONStructure();
structure.setNumber("num");
structure.setID("id");
structure.setLog("log");
String json = mapper.writeValueAsString(structure)
this.mockMvc.perform
(MockMvcRequestBuilders.post("http://localhost:8080/logError")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(MockMvcResultMatchers.status().isCreated())
.andReturn();
}
}
I am getting a NPE on line containing this.mockMvc.perform(...).
Can anyone point out what might the problem be?
You will get it to work in this way (i tested it):
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
#SpringBootTest
public class TestControllerImplTest {
#Mock
private TestBO TestBO; //if for example the controller calls some autowired service
private MockMvc mockMvc;
#InjectMocks //i think this was your main problem, you missed this annotation
TestControllerImpl controller;
#BeforeEach
void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test()
void shouldReturnAllTests() throws Exception {
TestDTO testDTO = new TestDTO();
testDTO.setId(Long.valueOf(1));
List<TestDTO> allTests = new ArrayList(Arrays.asList(testDTO));
when(testBO.getAllTests()).thenReturn(allTests);
mockMvc.perform(get("/api/test/getAllTests")).andExpect(status().isOk()).andDo(print());
}
}
When using #RunWith(SpringRunner.class), use #Autowired MockMvc mockmvc.
When using #RunWith(MockitoJunitRunner.class) or MockitoAnnotations.initMocks(this) then
use this.mockMvc = MockMvcBuilders.standaloneSetup(service).build();
Don't mix both Spring and Mockito runners.
Am new for Junit, any solution for below issue is welcomed.
I have a main class like,
#Service
public class MainClass extends AbstractClass {
#Autowired
ClassA a;
#Autowired
ObjectMapper mapper;
public void methodA(){
....
AnotherClass obj= (AnotherClass)mapper.readerFor(AnotherClass.class).readValue(SOME_CODE);
.......
}
Test Class is,
#RunWith(PowerMockRunner.class)
#PrepareForTest({MainClass.class})
public class MainClassTest {
#Mock
ClassA a;
#Mock
ObjectMapper mapper;
#InjectMocks
MainClass process = new MainClass();
//I have to do somthing for Autowired mapper class of main in test class as well
#Test
public void testProcessRequest() throws Exception{
process.methodA()
}
Am getting null for mapper object in main class while testing, Yes am aware that I haven't dne any kind of initialization.
Is there a better way for writing the junit mapper.
Note : I tried #Mock for ObjectMapper which throws exception at "readerFor".
Thanks in advance.
You do not have to use Mockito/powerMock. Just use a spring boot test.
Something like this:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.fasterxml.jackson.databind.ObjectMapper;
#RunWith(SpringRunner.class)
#SpringBootTest
public class SomeServiceTest {
#Autowired
private SomeService service;
#Autowired
private ObjectMapper om;
#Test
public void try_Me(){
System.out.println(om);
}
}
Adding a some info more to your question.
If you really want to use mockito for the ObjectMapper you should prepare the mock. If not when calling readerFor(...) the mock returns null by default and later, in the readValue method, you are getting a nullpointer.
A basic preparation for the mock might be:
ObjectReader or = Mockito.mock(ObjectReader.class);
Mockito.when(or.readValue(Mockito.anyString())).thenReturn(new instance of your object);
Mockito.when(mapper.readerFor(User.class)).thenReturn(or);
I want to use mockmvc to test controller which is recommended by Spring. But, I also have to use jmockit to mock the dependences.
The problem is that jmockit can't do well with mockmvc, whether the standaloneSetup() or the webAppContextSetup().
Another mocking tool named Mockito is well done with this problem, but it has a lot limits in mocking dependencies.
So, anybody has the experience or idea, please tell me. Thank you very much.
The example code is as following:
The first is the Mockito with Spring's MockMvc to unit test controller. This runs well.
public class TestControllerTest {
#InjectMocks
private LoginController loginController;
#Mock
private LoginService loginService;
private MockMvc mockMvc;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(loginController).build();
}
#Test
public void testLogin() throws Exception {
when(loginService.login()).thenReturn(false);
this.mockMvc.perform(get("/login"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(view().name("goodbyeworld"))
.andReturn();
}
}
Secondly, the jmockit is as following. Unfortunately, the loginController is null at the setup method. And, if i just invoke the loginController.xxx() in the #Tested method is fine. I think this shows that loginController is instantiated before #Tested method but after #Before method.
public class TestControllerTest2 {
#Tested
private LoginController loginController;
#Injectable
private LoginService loginService;
private MockMvc mockMvc;
#Before
public void setUp() throws Exception {
this.mockMvc = MockMvcBuilders.standaloneSetup(loginController).build();
}
#Test
public void testLogin() throws Exception {
new Expectations() {{
loginService.login(); result = false;
}};
this.mockMvc.perform(get("/login"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(view().name("goodbyeworld"))
.andReturn();
}
}
So, how can this problem be solved? jmockit's handful init method? any possible?
Differently from Mockito's #InjectMocks, JMockit's #Tested fields get created only after the execution of any #Before methods. This happens because of the support for mock parameters in test methods, which doesn't exist in Mockito. Arguably, tested fields should be set early, together with mock fields, so this may change in a future version of JMockit.
Anyway, solutions for the problem as it stands today are:
Do not use #Tested; instead, instantiate and inject the object under test manually in the #Before method.
Use #Tested, but avoid #Before methods which depend on tested fields. In the example test, the MockMvc object could be created in each test method by calling a MockMvc mockMvc() { return MockMvcBuilders... } method.
I have faced similar problem recently, and I have found a little bit of graceful solution:
#Tested(availableDuringSetup=true)
NotificationController notificationController;
#Injectable
NotificationService notificationService;
private MockMvc mockMvc;
#Before
public void init() {
this.mockMvc = MockMvcBuilders.standaloneSetup(notificationController).build();
}
boolean availableDuringSetup attribute for #Tested annotation is the solution :)
Hope that helps,
The problem is jmockit can't do well with mockmvc
I find that JMockit and Spring's MockMvc do play together well enough. I have successfully used the webAppContextSetup in my case. Here is an example which may not compile even, but could be a useful guide to get you started..
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import mockit.*;
import org.junit.*;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;
import some.package.Account;
import some.package.Collaborator;
#RunWith(SpringJUnit4ClassRunner.class)
#Transactional
#WebAppConfiguration
#ContextConfiguration(locations = { "classpath:/context/example1.xml", "classpath:/context/example2.xml" })
public class AccountControllerIntegrationTest {
private static final String PATH_TO_ACCOUNT = "/accounts/some_account";
private String exampleAccountJson = "{\"account\":\"Sample\",\"active\":true}";
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Mocked
private Account mockAccount;
#Mocked
private Collaborator mockCollaborator;
#Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
#Test
public void should_delete_account() throws Exception {
new Expectations() {{
mockAccount.getSomethingWhichReallyShouldNotBeExposed(); result = mockCollaborator;
mockCollaborator.getSomething(); result = "whatever";
}};
mockMvc.perform(delete(PATH_TO_ACCOUNT)).andExpect(status().isOk());
}
#Test
public void should_add_account() throws Exception {
new NonStrictExpectations() {{
mockAccount.getSomethingWhichReallyShouldNotBeExposed(); result = mockCollaborator;
mockCollaborator.getSomething(); result = "whatever";
}};
mockMvc.perform(put(PATH_TO_ACCOUNT).contentType(MediaType.APPLICATION_JSON).content(exampleAccountJson)).andExpect(status().isOk());
}
}
Hope it can help you--good luck!