春季靴子 - 无法使用Mockito of vishing()嘲笑,可选始终为空

发布于 2025-02-09 22:04:18 字数 3910 浏览 2 评论 0原文

我有要测试的控制器类:

public class AuthController implements AuthApi {
    private final UserService service;
    private final PasswordEncoder encoder;

    @Autowired
    public AuthController(UserService service, PasswordEncoder encoder) {
        this.service = service;
        this.encoder = encoder;
    }

    @Override
    public ResponseEntity<SignedInUser> register(@Valid NewUserDto newUser) {
        Optional<SignedInUser> createdUser = service.createUser(newUser);
        LoggerFactory.getLogger(AuthController.class).info(String.valueOf(createdUser.isPresent()));
        if (createdUser.isPresent()) {
            return ResponseEntity.status(HttpStatus.CREATED).body(createdUser.get());
        }
        throw new InsufficientAuthentication("Insufficient info");
    }

这是我的单位测试:

@ExtendWith(MockitoExtension.class)
@JsonTest
public class AuthControllerTest {

    @InjectMocks
    private AuthController controller;

    private MockMvc mockMvc;

    @Mock
    private UserService service;

    @Mock
    private PasswordEncoder encoder;

    private static SignedInUser testSignedInUser;

    private JacksonTester<SignedInUser> signedInTester;
    private JacksonTester<NewUserDto> dtoTester;

    @BeforeEach
    public void setup() {
        ObjectMapper mapper = new AppConfig().objectMapper();
        JacksonTester.initFields(this, mapper);
        MappingJackson2HttpMessageConverter mappingConverter = new MappingJackson2HttpMessageConverter();
        mappingConverter.setObjectMapper(mapper);
        mockMvc = MockMvcBuilders.standaloneSetup(controller)
                                 .setControllerAdvice(new RestApiErrorHandler())
                                 .setMessageConverters(mappingConverter)
                                 .build();
        initializeTestVariables();
    }

    private void initializeTestVariables() {
        testSignedInUser = new SignedInUser();
        testSignedInUser.setId(1L);
        testSignedInUser.setRefreshToken("RefreshToken");
        testSignedInUser.setAccessToken("AccessToken");
    }

    @Test
    public void testRegister() throws Exception {
        NewUserDto dto = new NewUserDto();
        dto.setEmail("[email protected]");
        dto.setPassword("ThisIsAPassword");
        dto.setName("ThisIsAName");
        // Given
        given(service.createUser(dto)).willReturn(Optional.of(testSignedInUser));

        // When
        MockHttpServletResponse res = mockMvc.perform(post("/api/v1/auth/register")
                                                     .contentType(MediaType.APPLICATION_JSON)
                                                     .content(dtoTester.write(dto).getJson()).characterEncoding("utf-8").accept(MediaType.APPLICATION_JSON))
                                             .andDo(MockMvcResultHandlers.print())
                                             .andReturn()
                                             .getResponse();
        // Then
        assertThat(res.getStatus()).isEqualTo(HttpStatus.CREATED.value());
        assertThat(res.getContentAsString()).isEqualTo(signedInTester.write(testSignedInUser).getJson());
    }
}

问题:

当我收到“不足信息”消息时,测试失败了,因为 iSpresent()也永远不会真实,即使给定(service.createuser(dto))。willreturn(可选。 (testSignedInuser))已经在那里。 当我尝试记录service.createuser(dto) 测试方法内时,其iSpresent()始终是正确的。 当我尝试在控制器方法中登录 时,它总是是错误的。

我尝试了什么:

我怀疑这是因为我的mockmvc是错误配置的,因此我尝试添加@AutoconFigureMockMvc 'MockMvc'“”。我试图将uservice模拟更改为实现类但无用。

请帮忙,我真的是新来的春季单元测试。谢谢你!

I have this Controller class that I want to test:

public class AuthController implements AuthApi {
    private final UserService service;
    private final PasswordEncoder encoder;

    @Autowired
    public AuthController(UserService service, PasswordEncoder encoder) {
        this.service = service;
        this.encoder = encoder;
    }

    @Override
    public ResponseEntity<SignedInUser> register(@Valid NewUserDto newUser) {
        Optional<SignedInUser> createdUser = service.createUser(newUser);
        LoggerFactory.getLogger(AuthController.class).info(String.valueOf(createdUser.isPresent()));
        if (createdUser.isPresent()) {
            return ResponseEntity.status(HttpStatus.CREATED).body(createdUser.get());
        }
        throw new InsufficientAuthentication("Insufficient info");
    }

This is my unit test:

@ExtendWith(MockitoExtension.class)
@JsonTest
public class AuthControllerTest {

    @InjectMocks
    private AuthController controller;

    private MockMvc mockMvc;

    @Mock
    private UserService service;

    @Mock
    private PasswordEncoder encoder;

    private static SignedInUser testSignedInUser;

    private JacksonTester<SignedInUser> signedInTester;
    private JacksonTester<NewUserDto> dtoTester;

    @BeforeEach
    public void setup() {
        ObjectMapper mapper = new AppConfig().objectMapper();
        JacksonTester.initFields(this, mapper);
        MappingJackson2HttpMessageConverter mappingConverter = new MappingJackson2HttpMessageConverter();
        mappingConverter.setObjectMapper(mapper);
        mockMvc = MockMvcBuilders.standaloneSetup(controller)
                                 .setControllerAdvice(new RestApiErrorHandler())
                                 .setMessageConverters(mappingConverter)
                                 .build();
        initializeTestVariables();
    }

    private void initializeTestVariables() {
        testSignedInUser = new SignedInUser();
        testSignedInUser.setId(1L);
        testSignedInUser.setRefreshToken("RefreshToken");
        testSignedInUser.setAccessToken("AccessToken");
    }

    @Test
    public void testRegister() throws Exception {
        NewUserDto dto = new NewUserDto();
        dto.setEmail("[email protected]");
        dto.setPassword("ThisIsAPassword");
        dto.setName("ThisIsAName");
        // Given
        given(service.createUser(dto)).willReturn(Optional.of(testSignedInUser));

        // When
        MockHttpServletResponse res = mockMvc.perform(post("/api/v1/auth/register")
                                                     .contentType(MediaType.APPLICATION_JSON)
                                                     .content(dtoTester.write(dto).getJson()).characterEncoding("utf-8").accept(MediaType.APPLICATION_JSON))
                                             .andDo(MockMvcResultHandlers.print())
                                             .andReturn()
                                             .getResponse();
        // Then
        assertThat(res.getStatus()).isEqualTo(HttpStatus.CREATED.value());
        assertThat(res.getContentAsString()).isEqualTo(signedInTester.write(testSignedInUser).getJson());
    }
}

Problem:

The test failed as I got the "Insufficient info" message, because isPresent() is never true, even with given(service.createUser(dto)).willReturn(Optional.of(testSignedInUser)) already there.
When I try to log service.createUser(dto) inside the test method, its isPresent() is always true.
When I try to log inside the controller method, it is always false.

What I have tried:

I suspect that it is because somehow my mockMvc is wrongly configured so I tried to add @AutoConfigureMockMvc but it ended up telling me that "There is no bean configured for 'mockMvc'". I tried to change the UserService mock to the implementation class of but no use.

Please help, I'm really new into Spring's unit tests. Thank you!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

等待我真够勒 2025-02-16 22:04:22

问题在于您找到了嘲讽的嘲笑:

given(service.createUser(dto)).willReturn(Optional.of(testSignedInUser))

具体来说,您指示模拟的服务返回optional.of(testSignedInuser)如果它接收到等于的参数dto。但是,根据equals() newuserdto的实现,这可能永远不会发生。例如,它仅在引用相同实例而不是比较成员变量的值时返回true。因此,当通过mockmvc传递dto时,它首先是序列化然后由对象映射器再次序列化,因此即使其成员变量具有相同的值,对象也是除非您还覆盖equals()方法,否则不相同。

作为替代方案,您可以放松模仿以返回optional.of(testSignedInuser)如果 any()参数已传递:

given(service.createUser(any())).willReturn(Optional.of(testSignedInUser))

或者如果参数 isa()

given(service.createUser(isA(NewUserDto.class))).willReturn(Optional.of(testSignedInUser))

doc/org.mockito/mockito-core/4.6.0/org/org/ mockito ,从测试的角度来看,首选要避免误报,因此我建议我仔细检查和 /或覆盖newuserdto#equals()< / code>方法(如果可能)。

The problem is as you have found the Mockito mocking:

given(service.createUser(dto)).willReturn(Optional.of(testSignedInUser))

Specifically, you instruct the mocked service to return an Optional.of(testSignedInUser) if it receives a parameter that is equal to dto. However, depending on the implementation of the equals() method of NewUserDto, this may never occur. For example it returns true only if the same instance is referred to instead of comparing the values of the member variables. Consequently, when passing a dto through the mockMvc it is first serialized and then serialized again by the object mapper, so even though its member variables have the same values, the objects are not considered equal unless you also override the equals() method.

As an alternative, you can relax the mocking to return the Optional.of(testSignedInUser) if any() argument is passed:

given(service.createUser(any())).willReturn(Optional.of(testSignedInUser))

or if the argument isA() specific class:

given(service.createUser(isA(NewUserDto.class))).willReturn(Optional.of(testSignedInUser))

but generally, it is preferred from a testing perspective to be explicit to avoid false positives so for this reason I advise to double check and and / or override the NewUserDto#equals() method if possible.

随梦而飞# 2025-02-16 22:04:22

多亏了@matsev的答案,我能够解决一半的问题,但也有一个问题:

@Controller
public class AuthController implements AuthApi {
  private final UserService service;
  private final PasswordEncoder encoder;
  // ...
}

我将服务字段留给final,这不允许Mockito注入或突变依赖关系。删除final后,我立即进行了测试。

编辑:请参阅此线程的解决方案 Mockito,@InigntMocks奇怪的行为

编辑2:通过设计,Mockito不会向最终字段注入模拟: https:/ /Github.com/mockito/mockito/issues/352
这样做违反了其他API,可能会引起问题。解决此问题的一种方法是仅使用构造函数注入,删除@InjectMocks,然后您可以只使用final字段。

// No InjectMocks
private AuthController controller;

@Mock
private UserService service;

@Mock
private PasswordEncoder encoder;
@BeforeEach
public void setup() {
   controller = new AuthController(service, encoder);
   // ...
}

Thanks to @matsev answer, I was able to solve half of the problem, but there was this thing as well:

@Controller
public class AuthController implements AuthApi {
  private final UserService service;
  private final PasswordEncoder encoder;
  // ...
}

I left the service fields as final, which did not allow Mockito to inject or mutate the dependency. After removing final, I got the test to work now.

EDIT: Please refer to this thread for workaround Mockito, @InjectMocks strange behaviour with final fields

EDIT 2: By design, Mockito does not inject mocks to final fields: https://github.com/mockito/mockito/issues/352
Doing so violates other APIs and can cause issues. One way to fix this is to just use constructor injection, remove @InjectMocks, then you can just use final fields.

// No InjectMocks
private AuthController controller;

@Mock
private UserService service;

@Mock
private PasswordEncoder encoder;
@BeforeEach
public void setup() {
   controller = new AuthController(service, encoder);
   // ...
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文