Home > Software design >  Mocking a method in a Service bean inside a Service bean (Spring Boot 2.5)
Mocking a method in a Service bean inside a Service bean (Spring Boot 2.5)

Time:10-01

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();
  }
}
  • Related