如何在 Spring-boot 中不模拟服务类的情况下为 REST 控制器端点编写单元测试

Posted

技术标签:

【中文标题】如何在 Spring-boot 中不模拟服务类的情况下为 REST 控制器端点编写单元测试【英文标题】:How to write Unit test for REST Controller Endpoint without mocking Service class in Spring-boot 【发布时间】:2016-12-26 00:30:06 【问题描述】:

我必须在 Spring-boot 中为 Web Service 编写单元测试,它应该从 Controller 执行测试并流入 Service 类。 我对 Service 类使用 @Mock 注释,对 Controller 使用 @InjectMock,因此应用程序流永远不会进入 Service 类,(它返回 null)。

如果 Service 类上没有 @Mock 注释,我会收到一个空点异常。

我不想模拟服务类,我想用同样的测试单元测试服务类。

这是我的 REST 控制器。

 @RestController
 public class AddressController 
 @Autowired
 private GetAddressService getAddressService;

@RequestMapping(value=ApplicationConstant.ADDRESS_URI,method=RequestMethod.G    ET,produces =  ApplicationConstant.APP_RESP_TYPE )
public @ResponseBody SampleResponse getAddress(
        @RequestParam(value="id",required=true) String hcId,
        @RequestParam(value="mbrNbrbr",required=true) String mbrNbr,
        )

    System.out.println("Request ID in Controller :"+Id);
    String responseid = getAddressService.getAddress(Id);

    SampleResponse smapleResp = new SampleResponse();
    System.out.println("Response ID in Controller :"+Id);
    smapleResp.setResponseid(responseid);
    return smapleResp;

这是我的服务类

 @Service
public class GetAddressServiceImpl implements GetAddressService 
@Override
public String getAddress(String id) 

    System.out.println("ID in Service : " + id);
    return id;


我有一个抽象测试类,它扩展到抽象控制器测试类,最后​​是我的地址控制器测试类,它有 @Test 方法。

抽象测试类:

 @RunWith(SpringJUnit4ClassRunner.class)
 @SpringApplicationConfiguration(classes = Application.class)
 public abstract class AbstractTest 
  

抽象控制器测试类:

@WebAppConfiguration 
public abstract class AbstractControllerTest extends AbstractTest 

//mockmvc stimulates Http interactions
protected MockMvc mockMvc; 

@Autowired
protected WebApplicationContext webApplicationContext;


protected void setUp()
    mockMvc =   MockMvcBuilders.webAppContextSetup(webApplicationContext).build();



protected void setUp(AddressController addressController)
    mockMvc = MockMvcBuilders.standaloneSetup(addressController).build();


最后是我的地址测试类:

   @TestExecutionListeners
    public class AddressMockControllerTest extends AbstractControllerTest  

    @Mock
    private GetAddressService getAddressService;

    @InjectMocks
    private AddressController addressController;

    private String MockURI ="/v1/legacy/member/Contact/Address? id=55555&mbrNbr=20&id=ABC&productid=1FGQ&sourceid=STAR&addressuid=adasdsa";

    @Before
    public void setUp()
        MockitoAnnotations.initMocks(this);
        setUp(addressController);       
        


    @Test
    public void test()
        System.out.println("In Unit test");

    

    @Test
    public void testgetAddress() throws Exception 


     System.out.println("In Unit test");

     MvcResult result =  this.mockMvc.perform(
                   MockMvcRequestBuilders.get(MockURI)
                  .accept(MediaType.APPLICATION_JSON))
                  .andReturn(); 


     String content = result.getResponse().getContentAsString();

        System.out.println("In Unit test and content is " + content);   
    

引导主类:

 public class Application extends SpringBootServletInitializer

public static void main(String[] args) 
    SpringApplication.run(ApplicationInitializer.class, args);


@Override
protected final SpringApplicationBuilder configure(final SpringApplicationBuilder application) 
    return application.sources(ApplicationInitializer.class);

应用初始化器:

    @Configuration

@EnableAutoConfiguration
@ComponentScan(value="com.ABC")
@EnableConfigurationProperties
public class ApplicationInitializer 

    @Bean
    @ConditionalOnMissingBean(RequestContextListener.class)
    public RequestContextListener requestContextListener() 
        return new RequestContextListener();
    

    @Bean
    public ServletRegistrationBean dispatcherRegistration(DispatcherServlet dispatcherServlet) 
        ServletRegistrationBean registration = new ServletRegistrationBean(
                dispatcherServlet);
        registration.addUrlMappings("/");
        return registration;
    

    @Bean
    public ServletRegistrationBean dynamicLogbackLevelServletRegistration() 

        return new ServletRegistrationBean(new DynamicLogbackLevelServlet(),"/loglevel");

    


    @Order(Ordered.HIGHEST_PRECEDENCE)
    public FilterRegistrationBean contextFilterRegistrationBean() 
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        RequestFilter contextFilter = new RequestFilter();
        registrationBean.setFilter(contextFilter);
        registrationBean.setOrder(1);
        return registrationBean;
    


    @Bean
    public ApplicationContextProvider applicationContextProvider()
        return new ApplicationContextProvider();
    


【问题讨论】:

你使用的是什么版本的 Spring Boot / Spring? 1.2.4。我不会使用 1.4 或任何其他版本。 你的Application.class 在连接服务 bean 时是什么样子的? 你能把它放在你的问题中,这样它就可以被格式化以便可读吗?请在课程中添加任何注释。 我已经添加了更多的细节,这是你要找的吗? 【参考方案1】:

顺便说一句,Spring Boot 现在具有基于 @MockedBean 注释的 built in support for mocking and spying beans。也许你想研究一下。

【讨论】:

谢谢,但我不会使用 Spring boot 1.4【参考方案2】:

不确定这是否可行,但基于另一个 SO 帖子:https://***.com/a/32294564/1499549

如果您正在使用跑步者,您似乎不应该使用standaloneSetup()。

public void setup() 
    mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();

然后去掉@InjectMocks注解,换成@Autowired

//@Mock
//private GetAddressService getAddressService;

//@InjectMocks
@Autowired
private AddressController addressController;

private String MockURI ="/v1/legacy/member/Contact/Address?id=55555&mbrNbr=20&id=ABC&productid=1FGQ&sourceid=STAR&addressuid=adasdsa";

@Before
public void setUp()
    setUp();       


@Test
public void test()
    System.out.println("In Unit test");

【讨论】:

@ShwanClark 我之前也试过这个,没用。在这种情况下,它似乎甚至没有启动测试。由于我使用了 TestExecutionListeners,它开始寻找 ProfileValueSourceConfiguration 但之后不再继续 我之前没有使用过@TestExecutionListeners 注释,但根据文档,您似乎实际上并没有利用任何侦听器,因为您没有在该注释上定义它们。我确实找到了另一个关于它的 SO 答案:***.com/a/27389768/1499549。我想知道您是否应该将DependencyInjectionTestExecutionListener 添加到该注释中?

以上是关于如何在 Spring-boot 中不模拟服务类的情况下为 REST 控制器端点编写单元测试的主要内容,如果未能解决你的问题,请参考以下文章

事务回滚在 JUnit5 的 @Nested 类的测试用例中不起作用

JPA 自动完成功能在 Intellij spring-boot kotlin 项目中不起作用

如何模拟从控制器测试类注入域类的服务?

带有 Spring-boot 后端的 Flutter websocket

后台获取在模拟器中工作但在真实设备中不工作

如何清理我的Android模拟器中不需要的应用程序?