[英]Spring Security causing problems when doing unit tests on REST controller
[英]Spring REST controller behaves differently in unit tests
我是 Spring 的新手,并试图为我的 REST controller 编写一些单元测试。 使用httpie
或curl
手动测试效果很好,但是使用@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.