How to use jmockit with spring mockmvc to test a controller
I want to use mockmvc
for testing a controller that Spring recommends. But I also have to use jmockit
mock dependencies.
The problem is that jmockit cannot handle mockmvc
, be it standaloneSetup()
or webAppContextSetup()
.
Another mocking tool called Mockito handles this problem well, but it has a lot of limitations in mocking dependencies.
So, does anyone have any experience or idea, please tell me. Thank you very much.
Sample code looks like this:
The first is Mockito with a Spring controller mockmvc
prior to unit test. It works 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();
}
}
Second, jmockit looks like this. Unfortunately loginController
null in the setup method. And, if I just call loginController.xxx()
in a method @Tested
, that's fine. I think this shows what loginController
is being created before the method @Tested
, but after the @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 do you fix this problem? jmockit handful init method? any possible?
source to share
Unlike Mockito @InjectMocks
, JMockit fields @Tested
are created only after any methods have been executed @Before
. This is due to the support for mock parameters in test methods that Mockito does not have. It is possible that the checked fields should have been set before, along with the false fields, so this may change in a future version of JMockit.
If anything, the solutions to the problem facing today are as follows:
- Do not use
@Tested
; instead instantiate and inject the object of interest manually in the method@Before
. - Use
@Tested
but avoid methods@Before
that depend on the checked fields. In the sample test, an objectMockMvc
can be created in each test method by calling the methodMockMvc mockMvc() { return MockMvcBuilders... }
.
source to share
Jmockit cannot succeed in mockmvc
I find that JMockit and Spring MockMvc play well together. I have used webAppContextSetup successfully in my case. Here's an example, which may not even compile, but can be a helpful guide to getting 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 this can help you - good luck!
source to share
I ran into a similar problem lately and I found a slightly neat 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 it helps,
source to share