Reputation: 63
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 -
Can someone please help me understand these behaviors.
Upvotes: 0
Views: 115
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:
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");
}
}
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
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