how to use jmockit with spring's mockmvc to test controller - spring

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!

Related

Set authentication for Integration test of secured Controller

I cannot set authentication for my integration test of rest controller. Controller's method looks like this:
#RestController
#RequestMapping(BASE_URL)
#RequiredArgsConstructor
public class EventController {
public static final String BASE_URL = "/api/event";
#PostMapping
#PreAuthorize("hasRole('USER')")
public void createEvent() {
System.out.println("I am in controller");
}
}
and here is my test:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class EventControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext context;
#BeforeEach
public void setup() {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
#Test
void create() throws Exception {
this.mockMvc.perform(post(EventController.BASE_URL)
.with(authentication(new UsernamePasswordAuthenticationToken(
new MyPrincipal(100, "denis"),
null,
Collections.singletonList(new SimpleGrantedAuthority("USER"))
)))
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"));
}
My test always failed due to status 401 so my mocked authentication doesn't work. Can you tell me how to fix it? Thank you in advice.
The easiest way to test secured requests is to use #WithMockUser(A_ROLE)
so your test could look like
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
#SpringBootTest
#AutoConfigureMockMvc
class EventControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
#WithMockUser("USER")
void create() throws Exception {
this.mockMvc.perform(post(EventController.BASE_URL)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"));
}
}
Some remarks:
your test expects a result, so adopt your controller or test
#PostMapping
#PreAuthorize("hasRole('ROLE_USER')")
public ResponseEntity<String> createEvent() {
String result = "I am in controller";
System.out.println(result);
return ResponseEntity.ok().body(result);
}
you are doing/testing a POST so make sure that in your security configuration you do http.csrf().disable()....
or provide a csrf-token in your test
this.mockMvc.perform(post(EventController.BASE_URL)
.with(SecurityMockMvcRequestPostProcessors.csrf()) // provide a csrf-token
....
'

Null Pointer Exception on MockMvc.perform on #Test class

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.

#WithMockUser doesn't pick Spring Security auth credentials

I have setup basic authentication in my controller with Spring Security in the classic way as follows:
#EnableWebSecurity
#EnableGlobalMethodSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("user").roles("USER")
.and()
.withUser("admin").password("admin").roles("USER", "ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(....);
}
}
When it comes to the point of testing, I am using #WithMockUser to annotate my tests. A test might look like this for GET:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = SomeController.class)
public class SomeControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void test1() {
mockMvc.perform(get(...)).andExpect(...);
}
or like this for POST:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = SomeController.class)
public class SomeControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void test1() {
mockMvc.perform(post(...)).andExpect(...);
}
then something unexpected happens:
when a test method is not annotated with #WithMockUser, it fails because of 401 status (Unauthorized) which is reasonable, because no basic authentication has been fullfilled
when a test method is simply annotated with an empty #WithMockUser without specifying ANY credentials, it starts passing, which is not reasonable, because I did not provide the correct data for authentication (rather I left them empty)
at this point a test method is passing also when filling in some correct credentials like in #WithMockUser(username = "user", password = "user", roles = "USER")
QUESTION: what's going on? How to fix this misbehaviour?
Looks like Spring Security is activated, however my testing is not using the authentication that I would expect to be used. Do I have to mock the authentication data myself?
EDIT The full configure method is the following
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(URL1).hasAnyRole(ROLE_ADMIN)
.antMatchers(URL2).hasAnyRole(ROLE_USER)
.antMatchers(URL3).permitAll()
.and()
.httpBasic()
.and()
.csrf().disable();
}
There are two reasons behind this behavior:
#WithMockUser annotation is not intended to execute authentication. It creates a user which is authenticated already. By default his credentials are user : password
#WebMvcTest does not execute MySecurityConfig.java. This annotation creates Spring mockMvc object with Security defaults for testing. Those security defaults are applied by org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityAutoConfiguration
You can double check this by putting break points on MySecurityConfig methods and rerunning your test in debug mode. Break points are not hit.
Solving issue 1
Simply change your approach to what #WithMockUser annotation does. It gives already logged-in user. It is still possible to test urls security and roles configuration with specifying concrete username, password and roles.
Solving issue 2
Create a base class for all Integration tests. It will configure mockMvc with Spring Security applied. Also note #SpringBootTest annotation. Now test will use MySecurityConfig.java
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.context.WebApplicationContext;
#RunWith(SpringRunner.class)
#SpringBootTest
public abstract class IT {
#Autowired
protected WebApplicationContext wac;
#Autowired
private FilterChainProxy springSecurityFilterChain;
protected MockMvc mockMvc;
#Before
public void applySecurity() {
this.mockMvc = webAppContextSetup(wac)
.apply(springSecurity(springSecurityFilterChain))
.build();
}
}
Rewrite the test like this. Assuming you use http basic authentication. Credentials are provided inside the test. Note: no mock user annotation.
package com.example.demo;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import org.junit.Test;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
public class SomeControllerIT extends IT {
#Test
public void test1() throws Exception {
mockMvc.perform(get("/some")
.with(httpBasic("user", "user")))
.andExpect(MockMvcResultMatchers.content().string("hello"));
}
}
Here is how you can run your mockMVC tests with your configuration of spring security: for the USER role...
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = SomeController.class)
public class SomeControllerTest {
#Autowired
private WebApplicationContext context;
#Autowired
private MockMvc mockMvc;
#Before
public void setup() {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.defaultRequest(get("/")
.with(user("user").password("password").roles("USER")))
.apply(springSecurity())
.build();
}
#Test
public void test1() {
mockMvc.perform(get(...)).andExpect(...);
}
}
after making this change your GET tests should now work.
since spring security provides cross site request forgery protection for http requests such as POST and DELETE, you need to run these particular tests with crsf()
#Test
public void shouldPost() {
mockMvc.perform(post(...)).with(csrf().asHeader())
.andExpect(...);
}
I faced the same issue. I could resolve it by with the #WithMockUser annotation with authorities specified.
#Test
#DisplayName("Should create and return the student")
#WithMockUser(username = "admin", authorities = { "ADMIN", "USER" })
public void should_create_student() throws Exception {
mockMvc.perform(post(...)).andExpect(...);
}
For information, this code has allowed me to successfully run a credentialed test, with credentials extracted from the application.properties file, without using an #WithMockUser annotation.
#Autowired
MockMvc mvc;
#Autowired
private WebApplicationContext context;
#MockBean
BookRepository bookRepository;
#Test
public void testFindAll() throws Exception {
mvc = MockMvcBuilders.webAppContextSetup(context).build();
this.mvc.perform(get("/books").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}

Failing while testing autowired field using Junit

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);

spring-boot Test JpaRepository is null

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

Resources