繁体   English   中英

Spring REST controller 在单元测试中表现不同

[英]Spring REST controller behaves differently in unit tests

问题

我是 Spring 的新手,并试图为我的 REST controller 编写一些单元测试。 使用httpiecurl手动测试效果很好,但是使用@WebMvcTest会发生奇怪的事情。

这是当我通过PUT curl一个新用户时会发生什么:

$ curl -v -H'Content-Type: application/json' -d@- localhost:8080/api/users <john_smith.json                                                                                                                                  
*   Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /api/users HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.69.1
> Accept: */*
> Content-Type: application/json
> Content-Length: 102
> 
* upload completely sent off: 102 out of 102 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sat, 18 Apr 2020 22:29:43 GMT
< 
* Connection #0 to host localhost left intact
{"id":1,"firstName":"John","lastName":"Smith","email":"john.smith@example.com","password":"l33tp4ss"}

如您所见,响应中存在 Content-Type header ,正文确实是新的User

以下是我尝试自动测试的方法:

@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
public class UserControllerTest {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private UserService service;

    private final User john = new User("John", "Smith",
                                       "john.smith@example.com",
                                       "s3curep4ss");

    @Test
    public void givenNoUser_whenCreateUser_thenOk()
    throws Exception
    {
        given(service.create(john)).willReturn(john);

        mvc.perform(post("/users")
                    .contentType(APPLICATION_JSON)
                    .content(objectToJsonBytes(john)))
        .andExpect(status().isOk())
        .andExpect(content().contentType(APPLICATION_JSON))
        .andExpect(jsonPath("$.id", is(0)))
        .andDo(document("user"));
    }

}

但我得到的是:

$ mvn test
[...]
MockHttpServletRequest:                                                                                                                
      HTTP Method = POST    
      Request URI = /users                   
       Parameters = {}                                                                                                                 
          Headers = [Content-Type:"application/json", Content-Length:"103"]                                                                                                                                                                                     
             Body = {"id":0,"firstName":"John","lastName":"Smith","email":"john.smith@example.com","password":"s3curep4ss"}
    Session Attrs = {}                                                                                                                                                                                                                                                        

Handler:                
             Type = webshop.controller.UserController
           Method = webshop.controller.UserController#create(Base)                                                                     

Async:                                        
    Async started = false                                                                                                              
     Async result = null                      

Resolved Exception:                       
             Type = null                                           

ModelAndView:                                                                                                                                                                                                                                                                 
        View name = null                                                                                                                                                                                                                                                      
             View = null                                                                                                                                                                                                                                                      
            Model = null                                                                                                               

FlashMap:                                                                                                                                                                                                                                                                     
       Attributes = null                                           

MockHttpServletResponse:    
           Status = 200                      
    Error message = null                                                                                                               
          Headers = []                                                                                                                                                                                                                                                        
     Content type = null                                                                                                               
             Body =                                                                                                                                                                                                                                                           
    Forwarded URL = null                                                                                                               
   Redirected URL = null
          Cookies = []                               
[ERROR] Tests run: 6, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 11.271 s <<< FAILURE! - in webshop.UserControllerTest          
[ERROR] givenNoUser_whenCreateUser_thenOk  Time elapsed: 0.376 s  <<< FAILURE!                                                         
java.lang.AssertionError: Content type not set
        at webshop.UserControllerTest.givenNoUser_whenCreateUser_thenOk(UserControllerTest.java:70)

怎么了? MockHttpServletResponse的主体在哪里? 我一定错过了什么,因为它的行为似乎完全不同。


其他代码以备不时之需

我的通用 controller class:

public class GenericController<T extends Base>
implements IGenericController<T> {

    @Autowired
    private IGenericService<T> service;

    @Override
    @PostMapping(consumes = APPLICATION_JSON_VALUE,
                 produces = APPLICATION_JSON_VALUE)
    public T create(@Valid @RequestBody T entity)
    {
        return service.create(entity);
    }

    /* ... Other RequestMethods ... */

}

实际User controller:

@RestController
@RequestMapping(path="/users")
public class UserController extends GenericController<User> { }

更新 2020-04-22
正如建议的那样,我将 generics 排除在外,但这并没有帮助。

似乎@WebMvcTest注释正在配置一个使用真实实现的UserService bean,并且您的 bean 以某种方式被忽略了。

我们可以尝试以不同的方式创建UserService bean

@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
@Import(UserControllerTest.Config.class)
public class UserControllerTest {

    @TestConfiguration
    static class Config {

        @Primary
        @Bean
        UserService mockedUserService() {
            UserService service = Mockito.mock(UserService.class);
            given(service.create(john)).willReturn(UserControllerTest.john());
            return service;
        }
    }

    static User john() {
        return new User("John", "Smith", "john.smith@example.com", "s3curep4ss");
    }

    ...
}

您还可以在测试中将存根移至@Before方法

@Configuration
public class CommonTestConfig {
   @Primary
   @Bean
   UserService mockedUserService() {
      return Mockito.mock(UserService.class)
   }
}

@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
@Import(CommonTestConfig.class)
public class Test1 {
   @Autowired
   private UserService userService;

   @Before
   public void setup() {
      given(userService.create(any())).willReturn(user1());
   }
}

@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
@Import(CommonTestConfig.class)
public class Test2 {
   @Autowired
   private UserService userService;

   @Before
   public void setup() {
      given(userService.create(any())).willReturn(user2());
   }
}

显然,问题出在这一行:

given(service.create(john)).willReturn(john);

当我将第一个john (这是一个User对象)更改为例如any()时,测试通过就好了。


有人可以为我解释一下为什么会这样吗? any()交换john是可行的,但感觉有点 hacky。 controller 将 JSON 反序列化john object 传递给其服务。 仅仅是反序列化的john显然与我在测试 class 中创建的 object 不同吗?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM