javaspringtestingmocking

Test with mocked service and another test with real implementation


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?


Solution

  • 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{}