【问题标题】:Spring Mockito - Junit Controller Test - Mock one serviceSpring Mockito - Junit 控制器测试 - 模拟一项服务
【发布时间】:2019-06-29 12:21:00
【问题描述】:

我有一个控制器类 ControllerClass 管理两个类服务:

  • ServiceA 解析一些文件

  • ServiceB 管理文件系统

我想测试 ControllerClass,尤其是:

  • ServiceA自动装配类

  • ServiceB 模拟此服务,使用实现接口的模拟类始终返回固定值。

我该怎么办?

【问题讨论】:

  • 如果您想要类的实际行为,请使用 spy。
  • 什么是间谍?你能告诉我有关
  • 你想模拟ServiceB而不模拟ServiceA吗?如果您正在编写单元测试,为什么不同时模拟两者?
  • Spy 应该不是必需的,当您在对控制器进行单元测试时调用您的服务时,只需返回模拟数据。除非我误解了什么?
  • 无论如何,对我来说,依赖于服务实际行为的测试听起来并不像单元测试。更像是集成测试。

标签: java spring spring-boot


【解决方案1】:

从一个简单的 spring-web 应用程序开始,进行以下测试:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MyControllerTest {

    @Autowired
    private MockMvc mvc;

    @Test
    public void testGreeting() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(content().string(equalTo("A response: I am real ServiceA!, B response: I am real ServiceB!")));
    }
}

解决方案一

使用@MockBean 和@Before:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MyControllerMockBeanTest {

    @MockBean
    private ServiceB mockB;

    @Before
    public void setup() {
       Mockito.when(mockB.greeting()).thenReturn("I am mock Service B!");
    }
    @Autowired
    private MockMvc mvc;

    @Test
    public void testGreetingMock() throws Exception {
       mvc.perform(MockMvcRequestBuilders.get("/")
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(content().string(equalTo("A response: I am real ServiceA!, B response: I am mock Service B!")));
    }
}

解决方案二

使用弹簧配置文件和自定义测试配置:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
// activate "test" profile
@ActiveProfiles("test")
// set custom config classes (don't forget Application)
@ContextConfiguration(classes = {TestConfig.class, Application.class})
public class MyControllerTest {
    // define configuration for "test" profile (inline possible)
    @Profile("test")
    @Configuration
    static class TestConfig {

        @Bean
        // !
        @Primary
        // I had an (auto configuration) exception/clash, 
        // when using *same bean name*, so *not* 'serviceB()', plz.
        public ServiceB mockB() {
            // prepare...
            ServiceB mockService = Mockito.mock(ServiceB.class);
            Mockito.when(mockService.greeting()).thenReturn("I am Mock Service B!");
            // and return your mock object!
            return mockService;
        }
    }
    @Autowired
    private MockMvc mvc;

    @Test
    public void testGreetingMock() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(content().string(equalTo("A response: I am real ServiceA!, B response: I am Mock Service B!")));
    }
}

Complete sample at github.


我很确定,解决方案列表不完整

...

【讨论】:

  • 这是一个相当复杂的例子,使用 Zgurskyi 所说的@MockBean 更清晰、更容易。
【解决方案2】:

@MockBean 看起来很适合您的用例。

此注释的行为方式如下:

  • 如果在测试上下文中没有定义 bean - 将添加新的
  • 如果已经定义了单个 bean - 那么它将被替换为 模拟
  • 如果已经定义了两个或更多 bean - 然后使用@Qualifier 指定哪个应该被 mock 替换

在您的测试类中连接模拟 bean 后,您可以对它进行存根,以始终返回一些值。例如,对于特定的测试,只需在您的测试中添加类似这样的内容:

    @Autowired
    private ServiceA serviceA;
    @MockBean
    private ServiceB serviceB;

    @Test
    public void testSomething() {
        when(serviceB.doSomething()).thenReturn("fixed response");
        // ...
    }

如果您想要所有测试的存根 - 将存根放在 setup 方法中:

    @Autowired
    private ServiceA serviceA;
    @MockBean 
    private ServiceB serviceB;

    @Before
    public void setup() {
        when(serviceB.doSomething()).thenReturn("fixed response");
    }

顺便说一句,Spring 还为@SpyBean 提供了与@MockBean 类似的行为。

spy 和 mock 基本上没有区别,如果你对它们进行 stub 方法调用。当方法调用没有被存根时,差异变得明显:

  • 在模拟的情况下 - 什么都不做(如果方法返回 void - 它只是没有被调用,如果方法返回一些东西 - 然后模拟将返回 null
  • 如果是间谍 - 调用真实对象的方法。

【讨论】:

  • 另一种可能性是编写服务 B 的模拟实现,并使用专用的 @Configuration 作为 @Primary @Bean 将其添加到测试中。
  • 是的,当然!这实际上是 Spring 如何在下面实现 @MockBean 的。缺点:你需要明确地做,@MockBean 已经做的事情。顺便说一句,Spring也提供了@SpyBean,当然你可以用普通的Mockito和@Configuration&@Primary @Bean来实现。
  • 据我了解,Spring 注释只给你一个 Mockito 实现。我提议的是编写一个专用的 ServiceB 接口的模拟实现,然后将其连接到上下文中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-08-18
  • 2014-12-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-22
  • 2013-04-16
  • 1970-01-01
相关资源
最近更新 更多