TL;DR: I want to test the controller method that uses a service and the service itself that is being called, both in their respective unit test classes but can't do it since mocked bean replaces original bean.
I have a service that I'm using in a controller.
public class SomeController {
@PostMapping("/load")
public ResponseEntity<String> someMethod(@RequestParam("val") String val) {
String lifeStatus = someService.serviceMethod(val);
return ResponseEntity.status(HttpStatus.OK).body(lifeStatus);
}
}
Let's say the service is this:
public class SomeServiceImpl implements SomeService {
public String serviceMethod(String val){
//business logic here
if (resultOfSuchBusinessLogic){
return "Life is good";
}
return "Life is not so good";
}
}
Then I wanted to create a test for this controller method. So I made a test config:
@Configuration
@ActiveProfiles("test") //So I can load application-test.properties
public class TestConfig {
@Autowired private SomeServiceMock someService;
@Bean
@Primary
SomeService someService() {
return someService;
}
}
And a MockService:
public class SomeServiceMock implements SomeService {
public String serviceMethod(String val){
if (val == "good"){
return "Life is good";
}
return "Life is not so good";
}
}
And in my test class for the controller I did this:
@ContextConfiguration(classes = TestConfig.class)
@SpringBootTest
@ActiveProfiles("test")
public class SomeControllerTests {
@Autowired private SomeController controller;
public void someTestOk() {
ResponseEntity someVar = controller.someMethod("someValue");
assertFalse(someVar.getBody(), "Life is good");
}
}
At this point this worked and I didn't care about the inner behavior of the SomeServiceImpl class. But then I did care. And I can't, for the love of me, figure it out what do I have to do so it uses the real implementation of the bean and not the mocked one. Because when I do this:
@SpringBootTest
@ActiveProfiles("test")
public class SomeServiceTests {
@Autowired private SomeService someService;
public void someTestOk() {
ResponseEntity someVar = someService.someMethod("someValue");
assertTrue(someVar.getBody(), "Life is good");
}
}
It still uses the Mocked one despite not having the ContextConfiguration. I found out that the Context is the same and when it creates a Bean then it will replace the original one. I tried to add the dirtycontext clean annotation but it still doesn't work. I also tried using the config class with mock:
@Bean
@Primary //(with and without this)
public SomeService someService() {
return mock(SomeService.class);
}
@BeforeAll method
when(someService.someMethod(anyString()).then(blah bla)
But still doesn't work. Either both use the original bean or both use the mocked. Any pointers at what I can be missing?
So I had two things going on here.
1 ) I was understading the profiles thing wrong. @ActiveProfiles("test") is needed so it loads the test properties. And I was using it in the same class that declared the mocked services.
2 ) So I changed the @Profile to ("mock") on my testconfig and added it into the tests that needed the mocked services. And also in the mocked services because it complained that there are 2 beans instead of one in the test that used the real implementation.
@SpringBootTest
@ActiveProfiles("test")
public class SomeServiceTests {}
@SpringBootTest
@ActiveProfiles({"test","mock"})
public class SomeControllerTests {}
@Configuration
@Profile("mock")
public class TestConfig {
@Bean
@Primary
public SomeService someService(){
return new SomeServiceMock()
}
}
@Service
@Profile("mock")
public class SomeServiceMock implements SomeService{}