Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@SpringBootTest: @MockBean not injected when multiple test classes

I want to write controller tests that also test my annotations. What I've read so far is that RestAssured one of the ways to go.

It works smoothly when I only have one controller test in place. However, when having 2 or more controller test classes in place, the @MockBeans seem to not be used properly. Depending on the test execution order, all tests from the first test class succeed, and all others fail.

In the following test run, the PotatoControllerTest was executed first, and then the 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.

I tried to fix it with a common ControllerTestBase which configures all mocks and all other controller tests extending the base class. Which worked fine on my machine, but e.g. not in the pipeline. So I guess it is not really stable.

Why is Spring not reloading the context with the mocks? Is this the "best" way of testing my controllers?

like image 576
Kyroy Avatar asked Nov 06 '22 06:11

Kyroy


1 Answers

It would be much easier and way faster to just use MockMvc.

You can just create a standalone setup for your desired controller and do additional configuration (like setting exception resolvers). Also you're able to inject your mocks easily:

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

Afterwards you can easily call your endpoints:

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

Additional validation on the response can be done like you already know it.

Edit: As a side note - this is designed to explicitly test your web layer. If you want to go for some kind of integration test going further down in your application stack, also covering business logic, this is not the right approach.

like image 145
tpschmidt Avatar answered Nov 09 '22 12:11

tpschmidt