Search code examples
javamockitojunit5springmockito

Using InjectMock and MockBean on static vs non static objects


I have a service class which uses a dependency -

MyService

@Service
@ToString
public class MyService{

    Dependency dependency;

    public MyService(Dependency dependency) {
        System.out.println("started MyService constructor");
        this.dependency = dependency;
        System.out.println("ended MyService constructor with dependency = " + this.dependency);
    }

    public void myMethod() {
        System.out.println("printing in myMethod : " + dependency.someMethod());
    }

    public void setDependency(Dependency dependency) {
        this.dependency = dependency;
        System.out.println("called setter with dependency = " + this.dependency);
    }
}

Dependency

@Component
public class Dependency {
    public String someMethod() {
        return "calling dependency someMethod";
    }
}

I wrote 2 test cases for MyService class -

@SpringBootTest
@ContextConfiguration(classes = {MyService.class})
class MyServiceTest {

    @InjectMocks
    MyService myService;

    @MockBean
    Dependency dependency;

    @Value("${someproperty}")
    private String someProperty;

    @BeforeEach
    void beforeEach() {
        System.out.println("beforeEach");
    }

    @Test
    void test1() {
        System.out.println("test1 start");
        when(dependency.someMethod()).thenReturn("calling mock dependency 1 someMethod");
        myService.myMethod();
        System.out.println("test1 end");
    }

    @Test
    void test2() {
        System.out.println("test2 start");
        when(dependency.someMethod()).thenReturn("calling mock dependency 2 someMethod");
        myService.myMethod();
        System.out.println("test2 end");
    }
}

I tried making MyService and Dependency static/non static in MyServiceTest and got following results -

  1. MyService static, Dependency static
    Constructor for MyService runs with dependency = null
    first test runs and fails
    setDependency is called and dependency is set to a mock object
    second test runs fine
  2. MyService static, Dependency non static
    Constructor for MyService runs with dependency = null
    first test runs and fails
    second test runs and fails
  3. MyService non static, Dependency static
    Constructor for MyService runs with dependency = null
    first test runs and fails
    Constructor for MyService runs with dependency = some mock object
    second test runs fine
  4. MyService non static, Dependency non static
    Constructor for MyService runs with dependency = null
    first test runs and fails
    Constructor for MyService runs with dependency = null
    second test runs fine

Can someone please help me understand these behaviors.


Solution

  • JUnit creates a new instance of the test class for every single test method that is executed. You must not use static fields, otherwise tests will not be isolated anymore. AFAIK the order of execution is not guaranteed to be deterministic.

    @MockBean is a Spring Boot annotation and @InjectMocks is a Mockito annotation. Running your test with SpringExtension will not process any Mockito extensions.

    You shouldn't mix Spring Boot and Mockito annotations, as that almost always doesn't do what you expect.

    • @InjectMocks will only inject @Spy and @Mock instances (via Reflection)
    • @MockBean will create a mocked bean instance and adds it to the application context. These beans will be picked up when processing @Autowired fields and constructors.

    Either way, both Spring Boot and Mockito annotations can only handle (only support) and inject non-static fields.

    So you need to decide if you want to write a Spring Boot test or a Mockito-based test:

    Mockito-based test

    This doesn't use the Spring application context at all and creates and injects all instances manually.

    @ExtendWith(MockitoExtension.class)   // process mockito extensions
    class MyServiceTest {
    
        @InjectMocks                      // inject any matching mock instances
        MyService myService;
    
        @Mock                             // create a mock instance
        Dependency dependency;
    
        @BeforeEach
        void beforeEach() {
            System.out.println("beforeEach");
        }
    
        @Test
        void test1() {
            System.out.println("test1 start");
            when(dependency.someMethod()).thenReturn("calling mock dependency 1 someMethod");
            myService.myMethod();
            System.out.println("test1 end");
        }
    
        @Test
        void test2() {
            System.out.println("test2 start");
            when(dependency.someMethod()).thenReturn("calling mock dependency 2 someMethod");
            myService.myMethod();
            System.out.println("test2 end");
        }
    }
    

    Spring Boot test

    This requires the Spring application context to be set up and uses it to look up and wire dependencies.

    @SpringBootTest           // use the correct annotation to enable component scanning
    class MyServiceTest {
    
        @Autowired            // autowire your real service dependency
        MyService myService;
    
        @MockBean             // added to the application context, will be injected into the MyService bean via the autowired constructor
        Dependency dependency;
    
        @BeforeEach
        void beforeEach() {
            System.out.println("beforeEach");
        }
    
        @Test
        void test1() {
            System.out.println("test1 start");
            when(dependency.someMethod()).thenReturn("calling mock dependency 1 someMethod");
            myService.myMethod();
            System.out.println("test1 end");
        }
    
        @Test
        void test2() {
            System.out.println("test2 start");
            when(dependency.someMethod()).thenReturn("calling mock dependency 2 someMethod");
            myService.myMethod();
            System.out.println("test2 end");
        }
    }