【问题标题】:Spring Boot @WebMvcTest having @MockBean null - using SpringRunner worksSpring Boot @WebMvcTest 具有 @MockBean null - 使用 SpringRunner 有效
【发布时间】:2022-01-20 13:50:14
【问题描述】:

简短: 从 Junit4 迁移到 JUnit5 有困难。删除@RunWith(SpringRunner.class) 后,我的@MockBean 注释服务为空,尽管我使用的是`@WebMvcTest'。为什么它不起作用?

长: 几天以来,我一直在为此苦苦挣扎。我正在对我的 Spring Boot 应用程序进行一些测试,使用 @RunWith(SpringRunner.class) im 与 @WebMvcTest 组合运行良好。

因为那是 JUnit4,所以我试图迁移到 Junit5。我从 pom 中删除了老式依赖项,并添加了 spring-boot-starter-test 的排除项。 JUnit 5 只剩下 junit-jupiter 依赖项。

我阅读了一些关于此的帖子和示例。由于我不想加载完整的上下文,所以 SpringBootTest 不是办法(而且根本不起作用) 我“认为”我发现@WebMvcTest 就足够了,因为它已经包含@extendWith 注释,这是@RunWith(SpringRunner.class) 的JUnit5 等价物。 所以:我只需要删除 springRunner 就可以了。

但是从现在开始,在删除带有@MockBean 注释服务的那一行之后,我的值为空。 我可以使用@WebMvcTest@MockBean 找到许多示例。所以我不明白,为什么我的不工作。 添加@RunWith(SpringRunner.class) 使其再次工作。

这就是测试类(没有springrunner):

package my.package;

import com.fasterxml.jackson.databind.ObjectMapper;
import my.package.controller.SkillTileCommand;
import my.package.controller.SkillTileController;
import my.package.controller.SkillTileResponse;
import my.package.service.SkillTileService;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import java.util.List;

import static org.hamcrest.Matchers.hasSize;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doNothing;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(SkillTileController.class)
public class SkillTileControllerIntegrationTest {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private SkillTileService skillTileService;

    private final SkillTileTestDataFactory testDataFactory = new SkillTileTestDataFactory();

    @Test
    public void givenSkillTiles_whenGETFindAll_thenReturnJsonArray() throws Exception {

        List<SkillTileResponse> skillTiles = testDataFactory.createTestSkillTileResponses(1, true, null, true);
        SkillTileResponse st1 = skillTiles.get(0);

        given(skillTileService.findAll(null)).willReturn(skillTiles);

        mvc.perform(get("/skill-tile/")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(1)))
                .andExpect(jsonPath("$[0].name").value(st1.getName()));
    }
}

使用 Spring runner 的工作方式如下:

package my.package;

import com.fasterxml.jackson.databind.ObjectMapper;
import my.package.controller.SkillTileCommand;
import my.package.controller.SkillTileController;
import my.package.controller.SkillTileResponse;
import my.package.service.SkillTileService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import java.util.List;

import static org.hamcrest.Matchers.hasSize;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doNothing;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@WebMvcTest(SkillTileController.class)
public class SkillTileControllerIntegrationTest {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private SkillTileService skillTileService;
[...]
}

愿服务负责人也有兴趣:

package my.package.service;

import lombok.RequiredArgsConstructor;
import my.package.controller.SkillTileCommand;
import my.package.controller.SkillTileResponse;
import my.package.exception.ResourceNotFoundException;
import my.package.repository.SkillTileEntity;
import my.package.repository.SkillTileRepository;
import my.package.utils.ObjectMapper;
import my.package.utils.ResourceUtility;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@RequiredArgsConstructor
@Service
public class SkillTileService {

    private final SkillTileRepository skillTileRepository;
[...]
}

以下是与测试相关的两个依赖项:

    [...]
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.8.2</version>
        <scope>test</scope>
    </dependency>
    [...]

感谢您的帮助。 最好不要只呆在 JUnit4。

编辑: 我对另一个仅用@DataJpaTest 注释并具有@Autowire 字段的测试类也有相同的行为。他们的 null 没有@RunWith

【问题讨论】:

  • 展示你的完整 pom。有了 spring starter,你不能像这样覆盖 JUnit 依赖项。
  • 您的 @Test 注释仍然来自 JUnit4,因此它也将与 Junit4 一起运行。忽略 @WebMvcTest(包括 JUnit5 注释),因此对注释不做任何事情。
  • @M.Deinum 上帝保佑你,请把它作为荣誉的答案

标签: spring-boot junit5


【解决方案1】:

如果您仔细查看您的测试,您会发现您的 @Test 注释仍然来自旧的 org.junit 包。这是JUnit4。这意味着您的测试实际上是使用 JUnit4 而不是 JUnit5 运行的。这基本上忽略了所有注释,因此也忽略了@WebMvcTest

要修复,请确保您的 @Test 注释也来自正确的 org.junit.jupiter.api 包,以使其成为正确的 JUnit5 测试。

【讨论】:

    【解决方案2】:

    您有可能在控制器中使用@RequiredArgsConstructor

    确保您已将服务字段指定为final@RequiredArgsConstructor 文档声明将使用未初始化的 final 或 @NonNull 字段的参数创建构造函数。如果 SkillTileService 依赖没有被标记为 final 它不会被添加为构造函数参数。

    因此,虽然 mock 本身不为 null,但它仍然可能永远不会注入到测试的控制器中。

    【讨论】:

    • 感谢您的回复。对我来说,听起来您认为我的控制器内部没有设置模拟。那是,我猜,不是这样的。它在我的测试类中而不是在控制器类中。它不是缺少控制器,而是该控制器调用的服务类。我的“SkillTileController”有“@RequiredArgsConstructor”和“private final SkillTileService SkillTileService;”。我的“SkillTileControllerIntegrationTest”(出现问题的地方)不是“@RequiredArgsConstructor”而是“@MockBean private final SkillTileService SkillTileService;”所以?我认为那是正确的?
    • @Kaspatoo,感谢您的澄清。是的,好像我误解了你的问题。如果我对您的问题有什么想法,我会在这里发布。
    猜你喜欢
    • 2019-09-07
    • 2020-01-10
    • 2019-04-30
    • 1970-01-01
    • 1970-01-01
    • 2019-04-28
    • 2020-06-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多