I've read tons of questions and answers on inner Mocking in Spring but I can't put to work my scenario...
I need to unit test a Spring Service bean (A) that @Autowire
another service bean (B) and executes a method in it, B.method(). This method is executed in the @PostConstruct
method of bean (A). I am using Spring Boot 2.5 (JUnit 5).
I @Autowire
bean (A) in my test class but I can't find a way to mock B.method() so when I run @Autowire
(A), it autowires (B), and when A executes B.method(), the method is Mocked.
I have tried with @Spy
, @SpyBean
, @MockBean
, ...
class TestClass {
@Autowired
private A a;
@Test
mytest () {
a.anyMethod();
}
}
@Service
class A {
@Autowired B b;
@Postconstruct
public void postconstruct() {
b.methodToBeMocked();
}
}
@Service
class B {
public void methodTobeMocked(){
}
}
CodePudding user response:
Being tricky it might point out that the design of your application could perhaps be improved.
The @MockBean
replaces an existing bean for the test execution. The @PostConstruct
is called earlier.
You could use a @TestConfiguration
to provide an already mocked and primed bean to your context.
I have added a simple, executable, example:
package de.trion.training;
@SpringBootTest(classes = {MockingSample.class, MockingSample.B.class,
MockingSample.A.class, MockingSample.MockInit.class})
public class MockingSample
{
@Autowired
private A a;
//too late!
//@MockBean
//private static B b;
//@BeforeEach
//void setUp()
//{
// when(b.methodToBeMocked()).thenReturn("mocked!");
//}
@TestConfiguration
static class MockInit
{
@Primary
@Bean
B makeB()
{
var b = mock(B.class);
when(b.methodToBeMocked()).thenReturn("mocked!");
return b;
}
}
@Test
void mytest()
{
a.anyMethod();
}
@Service
public static class A
{
@Autowired
private B b;
private String result;
@PostConstruct
public void postconstruct()
{
result = b.methodToBeMocked();
System.out.println("postconstruct: " result);
}
public void anyMethod()
{
System.out.println("anymethod: " result);
}
}
@Service
public static class B
{
public String methodToBeMocked()
{
return "real";
}
}
}
CodePudding user response:
Try to make your unit test as simple as possible which does not require starting up Spring in order to test it. You are testing A 's codes but not testing about if the Spring configures and initialises A properly.
That means you can simply manually inject B into A through constructor , and manually invoke @Postconstruct
method before testing any A 's methods.
You also don't need @SpringBootTest
. Just a plain JUnit5 test with Mockito somethings like below should be enough. It will also make your test run faster as it does not need to bootstrap Spring.
@ExtendWith(MockitoExtension.class)
public class ATest {
@Mock
private B b;
private A a;
@BeforeEach
public void init(){
a = new A(b);
}
@Test
public void fooTest(){
//stub B method
when(b.methodToBeMocked()).thenReturn("xxxxxxxx");
//manually call @Postconstruct before starting testing on A
a.postconstruct();
//start your testing on A
a.someMethod();
}
}