如何使用 jmockit 和 spring 的 mockmvc 来测试控制器

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

我想用mockmvc来测试Spring推荐的控制器。但是,我还必须使用 jmockit 来模拟依赖项。

问题是 jmockit 不能很好地处理 mockmvc,无论是 standaloneSetup() 还是 webAppContextSetup()

另一个名为 Mockito 的模拟工具很好地解决了这个问题,但是它在模拟依赖项方面有很多限制。

所以,有谁有经验或者想法,请告诉我。非常感谢。

示例代码如下:

首先是 Mockito 与 Spring 的 MockMvc 单元测试控制器。这运行良好。

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

其次,jmockit如下。不幸的是, loginController 在设置方法中为空。而且,如果我只是在 @Tested 方法中调用 loginController.xxx() 就可以了。我认为这表明 loginController@Tested 方法之前但在 @Before 方法之后实例化。

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

那么,如何解决这个问题呢? jmockit 的少数 init 方法?有可能吗?

The problem is jmockit can't do well with mockmvc

我发现 JMockit 和 Spring 的 MockMvc 一起玩得很好。在我的案例中,我已经成功地使用了 webAppContextSetup。这是一个可能甚至无法编译的示例,但可能是帮助您入门的有用指南..

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

}

希望对您有所帮助--祝您好运!

与 Mockito 的 @InjectMocks 不同,JMockit 的 @Tested 字段仅在 执行任何 @Before 方法后创建。发生这种情况是因为在测试方法中支持模拟参数,这在 Mockito 中不存在。可以说,测试字段应该与模拟字段一起尽早设置,因此这可能会在 JMockit 的未来版本中改变。

无论如何,目前的问题解决方案是:

  1. 不要使用@Tested;相反,在 @Before 方法中手动实例化并注入被测对象。
  2. 使用@Tested,但避免使用依赖于测试字段的@Before方法。在示例测试中,可以通过调用 MockMvc mockMvc() { return MockMvcBuilders... } 方法在每个测试方法中创建 MockMvc 对象。

最近遇到了类似的问题,找到了一点优雅的解决方法:

@Tested(availableDuringSetup=true)
NotificationController notificationController;

@Injectable
NotificationService notificationService;

private MockMvc mockMvc;

@Before
public void init() {
    this.mockMvc = MockMvcBuilders.standaloneSetup(notificationController).build();
}

boolean availableDuringSetup @Tested 注释的属性是解决方案:)

希望对您有所帮助,