How to create reusable @MockBean definitions in @SpringBootTest?
Asked Answered
M

3

6

I have a @SpringBootTest class that has a rather complex mock definition setup with mocked return values.

Question: can I externalize @MockBean setups into an own class, so I could reuse the mock configuration in multiple class (sidenote: I'm not looking for inheritance here!).

@SpringBootTest
public class ServiceTest extends DefaultTest {
    @Autowired
    private ServiceController controller;
    
    @MockBean
    private Service1 s1;
    
    @MockBean
    private Service2 s2;
    
    @MockBean
    private Service3 s3;
    
    //assume more complex mock definitions
    @BeforeEach
    public void mock() {
        when(s1.invoke()).thenReturn(result1);
        when(s2.invoke()).thenReturn(result2);
        when(s3.invoke()).thenReturn(result3);
    }
    
    @Test
    public void test() {
        //...
    }
}

I want to load the mocks independently of each other, not globally for all my tests.

Medical answered 12/4, 2021 at 7:13 Comment(0)
T
11

Not directly what you are asking for, but one possibility is to not use @MockBean but instead define your reusable mocks as @Primary @Beans in multiple @TestConfigurations that you can selectively @Import in your tests:

@TestConfiguration
public class MockService1 {

  @Bean
  @Primary
  public Service1 service1Mock() {
    Service1 s1 = Mockito.mock(Service1.class);
    when(s1.invoke()).thenReturn("result1");
    return s1;
  }
}

There's a nice article about this approach: Building Reusable Mock Modules with Spring Boot.

Theomancy answered 29/7, 2021 at 10:38 Comment(5)
And then using @Import(MockService1.class) on my tests? That's indeed a nice idea, I always struggled with this because it's impossible to inject @MockBean in this case. But the Mockito.mock() seems to work!Medical
@Medical yes, just @Import any mock you need in your test.Theomancy
Alternatively, you can define configuration with @SpringBootTest(classes = {MockService1.class})Zinfandel
Any idea how to combine this with the extension (@ExtendWith) mechanism? The use case is that I want to some setup in the extension which also mocks (among other things) some methods on some beans. So I would like to ensure that by using that extension the mocked beans are also imported.Metatherian
@Theomancy If I have two test classes, ATest.java and BTest.java both doing @Import(MockService1.class) and using the bean Service1 , if ATest also doing @DirtiesContext(classMode = ClassMode.AFTER_CLASS) in that case is Spring guaranteed to create all beans inside MockService1 before the execution of BTest.java?Indigence
M
1

Just for reference (if the @TestConfiguration approach might not be suitable for whatever reason), it also works when creating a junit Extension:

public class MockService1Extension implements BeforeTestExecutionCallback {
    @Override
    public void beforeTestExecution(ExtensionContext extensionContext) throws Exception {
        ApplicationContext springContext = SpringExtension.getApplicationContext(extensionContext);
        Service1 s1 = springContext.getBean(Service1.class);
        when(s1.invoke()).thenReturn("result1");
    }
}


@ExtendWith(MockService1Extension1.class)
@MockBean({Service1.class})
public class TestImpl {
    @Test
    public void test() {
    }
}

Unfortunately, for this approach, the implementing test must list the beans mocked inside the extension additionally with @MockBean on class level.

Medical answered 29/7, 2021 at 15:50 Comment(0)
H
-1

I think your answer lies here: https://mcmap.net/q/1771654/-how-to-share-mockbeans-and-mock-methods-among-my-tests-without-inheritance

Create a test profile that implements those MockBeans like so:

@Profile("test")
@Configuration
public class MyMockConfiguration {

    @Bean
    public SomeService someService() {
        SomeService someService = mock(SomeService .class);
        // mocked methods and results
        return someService ;
    }

And then use that profile with these annotations on your test class:

@ActiveProfiles("test")
@SpringBootTest
@WebAppConfiguration
@RunWith(SpringRunner.class)
Hall answered 29/7, 2021 at 9:54 Comment(2)
Well, but I would want to load the mocked beans selective! This would enable the mocks always for all tests using the "test" profile (or I would have to create N testprofiles - which is not good either), and is thus of no advantage over simple inheritance from an abstract testclass.Medical
Ah my bad I misunderstood. You might wanna add to your question that you want to load the mocks independently of one anotherHall

© 2022 - 2024 — McMap. All rights reserved.