【问题标题】:@SpringBootTest: @MockBean not injected when multiple test classes@SpringBootTest:多个测试类时没有注入@MockBean
【发布时间】:2020-11-03 11:43:10
【问题描述】:

我想编写控制器测试来测试我的注释。到目前为止,我所读到的是 RestAssured 是一种可行的方法。

当我只有一个控制器测试到位时,它可以顺利运行。但是,当有 2 个或更多控制器测试类时,@MockBeans 似乎没有正确使用。 根据测试执行顺序,第一个测试类中的所有测试都成功,其他所有测试都失败。

在下面的测试运行中,首先执行的是 PotatoControllerTest,然后是 FooControllerTest。

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles({"test", "httptest"})
class FooControllerTest {

    @MockBean
    protected FooService mockFooService;

    @MockBean
    protected BarService mockBarService;

    @LocalServerPort
    protected int port;

    @BeforeEach
    public void setup() {
        RestAssured.port = port;
        RestAssured.authentication = basic(TestSecurityConfiguration.ADMIN_USERNAME, TestSecurityConfiguration.ADMIN_PASSWORD);
        RestAssured.requestSpecification = new RequestSpecBuilder()
                .setContentType(ContentType.JSON)
                .setAccept(ContentType.JSON)
                .build();
    }

    @SneakyThrows
    @Test
    void deleteFooNotExists() {
        final Foo foo = TestUtils.generateTestFoo();
        Mockito.doThrow(new DoesNotExistException("missing")).when(mockFooService).delete(foo.getId(), foo.getVersion());

        RestAssured.given()
                .when().delete("/v1/foo/{id}/{version}", foo.getId(), foo.getVersion())
                .then()
                .statusCode(HttpStatus.NOT_FOUND.value());
        Mockito.verify(mockFooService, times(1)).delete(foo.getId(), foo.getVersion());
    }

...
}
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles({"test", "httptest"})
class PotatoControllerTest {

    @MockBean
    protected PotatoService mockPotatoService;

    @LocalServerPort
    protected int port;

    @BeforeEach
    public void setup() {
        RestAssured.port = port;
        RestAssured.authentication = basic(TestSecurityConfiguration.ADMIN_USERNAME, TestSecurityConfiguration.ADMIN_PASSWORD);
        RestAssured.requestSpecification = new RequestSpecBuilder()
                .setContentType(ContentType.JSON)
                .setAccept(ContentType.JSON)
                .build();
    }

...

}
Wanted but not invoked:
fooService bean.delete(
    "10e76ae4-ec1b-49ce-b162-8a5c587de2a8",
    "06db13f1-c4cd-435d-9693-b94c26503d40"
);
-> at com.xxx.service.FooService.delete(FooService.java:197)
Actually, there were zero interactions with this mock.

我试图用一个通用的ControllerTestBase 来修复它,它配置了所有模拟和所有其他扩展基类的控制器测试。在我的机器上运行良好,但是例如不在管道中。所以我猜它不是很稳定。

为什么 Spring 不使用模拟重新加载上下文?这是测试我的控制器的“最佳”方式吗?

【问题讨论】:

  • 我很惊讶没有更多的人遇到这个问题。 MockBean 基本上无法与多个集成测试类一起使用。

标签: java spring-boot unit-testing controller-tests


【解决方案1】:

只使用MockMvc 会更容易、更快。

您可以为所需的控制器创建一个独立的设置并进行额外的配置(例如设置异常解析器)。您还可以轻松地注入您的模拟:

@Before
public void init() {
  MyController myController = new MyController(mock1, mock2, ...);
  MockMvc mockMvc = 
  MockMvcBuilders.standaloneSetup(myController)
                 .setHandlerExceptionResolvers(...)
                 .build();
}

之后您可以轻松调用端点:

MvcResult result = mockMvc.perform(
            get("/someApi"))
             .andExpect(status().isOk)
             .andReturn();

可以像您已经知道的那样对响应进行额外验证。

编辑:附带说明 - 这旨在明确测试您的 Web 层。如果您想在应用程序堆栈中进一步进行某种集成测试,还涵盖业务逻辑,那么这不是正确的方法。

【讨论】:

  • 我也尝试过使用 RestAssuredMockMvc,但遇到了很多其他问题。在您发表评论后,我对测试感到非常恼火,因此我现在将其重构为 MockMvc。至少这行得通。仍然很奇怪,这是行不通的。
  • 我相信您采用这种方法并没有做错任何事 :) 很高兴它成功了。如果您要针对实际服务进行 e2e 测试,您可以再次进行 RestAssured。
猜你喜欢
  • 1970-01-01
  • 2017-05-11
  • 1970-01-01
  • 2019-09-15
  • 2019-11-18
  • 2022-10-20
  • 1970-01-01
  • 2022-01-04
  • 2021-12-02
相关资源
最近更新 更多