Sarthak
Sarthak

Reputation: 63

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.

Upvotes: 0

Views: 115

Answers (2)

knittl
knittl

Reputation: 265131

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");
    }
}

Upvotes: 2

lance-java
lance-java

Reputation: 27958

@MockBean should only be used when you are using @ComponentScan, @Configuration, @ContextConfiguration and/or @SpringBootTest etc to load a whole lot of spring beans but you want mock a handful for testing. If you are not overriding beans from another @Configuraiton you should use @Mock instead of @MockBean

Personally I avoid @MockBean usage all together since it causes many problems (it forces the creation of a new application context when you might expect the application context to be shared between tests).

I think @Mock will work for your test. Eg

@ExtendWith(SpringExtension.class)
class MyServiceTest {

    @InjectMocks
    MyService myService;

    @Mock
    Dependency dependency;

    ...
}

Also, you should get rid of the setDependency(...) method in MyService and make dependency final. Eg

public class MyService{
    private final Dependency dependency;

    public MyService(Dependency dependency) {
        this.dependency = dependency;
    }
    ...
}

If you really want to use @MockBean perhaps there's an issue with how @MockBean and @InjectMocks work together. In which case you could fix via

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ...)
class MyServiceTest {
    private MyService myService;

    @MockBean
    private Dependency dependency;

    @BeforeEach
    void beforeEach() {
        myService = new MyService(dependency);
    }
    ...
}

Upvotes: 1

Related Questions