Mock Spring service in controller test using Spock - spring-boot

I am looking for a way to mock a Service bean used in Controller so I can test only controller using MockMvc. But I can't find an easy way to replace real bean with Spock mock. Everything uses spring-boot 1.3.2 version. More details below:
I have a following controller class
#RestController
#RequestMapping(path = "/issues")
#AllArgsConstructor(onConstructor = #__(#Autowired))
public class NewsletterIssueController {
private final GetLatestNewsletterIssueService latestNewsletterIssueService;
#RequestMapping(
method = RequestMethod.GET,
path = "/latest"
)
public ResponseEntity getLatestIssue() {
Optional<NewsletterIssueDto> latestIssue = latestNewsletterIssueService.getLatestIssue();
if (latestIssue.isPresent()) {
return ResponseEntity.ok(latestIssue.get());
} else {
return ResponseEntity.notFound().build();
}
}
}
And Integration Spock test for this class:
#ContextConfiguration(classes = [Application], loader = SpringApplicationContextLoader)
#WebAppConfiguration
#ActiveProfiles("test")
class NewsletterIssueControllerIntegrationSpec extends Specification {
MockMvc mockMvc
#Autowired
GetLatestNewsletterIssueService getLatestNewsletterIssueService
#Autowired
WebApplicationContext webApplicationContext
def setup() {
ConfigurableMockMvcBuilder mockMvcBuilder = MockMvcBuilders.webAppContextSetup(webApplicationContext)
mockMvc = mockMvcBuilder.build()
}
def "Should get 404 when latest issue does not exist"() {
given:
getLatestNewsletterIssueService.getLatestIssue() >> Optional.empty() // this won't work because it is real bean, not a Mock
expect:
mockMvc.perform(MockMvcRequestBuilders
.get("/issues/latest")
.contentType(JVM_BLOGGERS_V1)
.accept(JVM_BLOGGERS_V1)
).andExpect(MockMvcResultMatchers.status().isNotFound())
}
}
I need a way to replace this autowired bean with a Mock/Stub so I can define interactions in 'given' section.

I'd create a local configuration in the test and override the bean there.
I don't know Groovy, but it would like this in Java:
#ContextConfiguration(classes = NewsletterIssueControllerIntegrationSpec.Conf.class, loader = SpringApplicationContextLoader.class)
#WebAppConfiguration
#ActiveProfiles("test")
class NewsletterIssueControllerIntegrationSpec extends Specification {
#Configuration
#Import(Application.class)
public static class Conf {
#Bean
public GetLatestNewsletterIssueService getLatestNewsletterIssueService() {
return mock(GetLatestNewsletterIssueService.class);
}
}
// […]
}
Caveat: This approach works well with Mockito, but you might need a pre-release version of Spock for it to work, ref: https://github.com/spockframework/spock/pull/546
By the way: Spring Boot 1.4 will provide a #MockBean construction to simplify this.

With Spock 1.2 you can use SpringBean annotation to inject mocked service in spring context https://objectpartners.com/2018/06/14/spock-1-2-annotations-for-spring-integration-testing/
So with this new annotation your test would be :
#WebMvcTest(controllers = [NewsletterIssueController], secure = false)
#AutoConfigureMockMvc
#ActiveProfiles("test")
class NewsletterIssueControllerIntegrationSpec extends Specification {
#Autowired
MockMvc mockMvc
#SpringBean
GetLatestNewsletterIssueService getLatestNewsletterIssueService
def setup() {
// nothing to do as SpringBean and WebMvcTest do the job for you
}
def "Should get 404 when latest issue does not exist"() {
given:
getLatestNewsletterIssueService.getLatestIssue() >> Optional.empty() // this won't work because it is real bean, not a Mock
expect:
mockMvc.perform(MockMvcRequestBuilders
.get("/issues/latest")
.contentType(JVM_BLOGGERS_V1)
.accept(JVM_BLOGGERS_V1)
).andExpect(MockMvcResultMatchers.status().isNotFound())
}
}

Related

Spring Boot JUnit tests fail with Status expected:<200> but was:<404>

For some time I've been struggling to make JUnit tests for my rest controller. For some reason, every time I try to run them I get the error Status expected:<200> but was:<404>. Here is my controller:
#RestController
#RequestMapping("/travels")
#RequiredArgsConstructor
public class TravelController {
private final TravelService travelService;
private final TravelOutputDtoMapper travelOutputDtoMapper;
#GetMapping
public List<TravelOutputDto> getAll() {
List<Travel> travels = travelService.getAll();
return travels.stream()
.map(travelOutputDtoMapper::travelToTravelOutputDto)
.collect(Collectors.toList());
}
}
And here is my test:
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = TravelController.class)
#ContextConfiguration(classes = {
TravelOutputDtoMapper.class,
TravelOutputDtoMapperImpl.class
})
class TravelControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private TravelService travelService;
#Autowired
private TravelOutputDtoMapper travelOutputDtoMapper;
#Test
void testGetAll() throws Exception {
List<Travel> travels = mockTravelList();
Mockito.when(travelService.getAll()).thenReturn(travels);
mockMvc.perform(get("/travels"))
.andExpect(status().isOk());
}
private List<Travel> mockTravelList() {
// Dummy travel list
}
}
I think the reason is connected with TravelOutputDtoMapper as if I remove it from the controller and don't try to inject it the tests are passing, but I cannot find any information why it is doing it. The autowired mapper has an instance and works just fine.
Here is the Mapper:
#Mapper(componentModel = "spring")
public interface TravelOutputDtoMapper {
#Mapping(target = "from", source = "entity.from.code")
#Mapping(target = "to", source = "entity.to.code")
TravelOutputDto travelToTravelOutputDto(Travel entity);
}
The #ContextConfiguration annotation is used for a different purpose:
#ContextConfiguration defines class-level metadata that is used to determine how to load and configure an ApplicationContext for integration tests.
Using Spring Boot and #WebMvcTest there's no need to manually specify how to load the context. That's done for you in the background.
If you'd use this annotation, you'd specify your main Spring Boot class here (your entry-point class with the #SpringBootApplication annotation).
From what I can see in your test and your question is that you want to provide an actual bean for the TravelOutputDtoMapper, but mock the TravelService.
In this case, you can use #TestConfiguration to add further beans to your sliced Spring TestContext:
// #ExtendWith(SpringExtension.class) can be removed. This extension is already registered with #WebMvcTest
#WebMvcTest(controllers = TravelController.class)
class TravelControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private TravelService travelService;
#Autowired
private TravelOutputDtoMapper travelOutputDtoMapper;
#TestConfiguration
static class TestConfig {
#Bean
public TravelOutputDtoMapper travelOutputDtoMapper() {
return new TravelOutputDtoMapper(); // I assume your mapper has no collaborators
}
}
// ... your MockMvc tests
}

How to #Autowired a test controller invoked by WebTestClient in #WebFluxTest?

I have a Controller just for tests with dummy apis
#RestController
public class TestController {
#Autowired
private org.springframework.cloud.sleuth.Tracer tracer;
#GetMapping("/trace")
public Mono<String> traceTest() {
...
}
}
Here's my test
#WebFluxTest(controllers = TestController.class)
public MyTest {
#Autowired
private WebTestClient webClient;
#Test
public void testTrace() {
webClient.get().uri("/trace")...
}
}
When I try to run this, my Tracer is not Autowired.
Of course, if I change my test to a #SpringBootTest, it all works
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public MyTest {
...
}
But I'd like to avoid autowiring my entire spring application. How can I get TestController to be auto-configured?
The #WebFluxTest annotation populates a Spring TestContext with only a subset of the relevant beans:
#WebFluxTest auto-configures the Spring WebFlux infrastructure and
limits scanned beans to #Controller, #ControllerAdvice,
#JsonComponent, Converter, GenericConverter, WebFilter, and
WebFluxConfigurer. Regular #Component and #ConfigurationProperties
beans are not scanned when the #WebFluxTest annotation is used.
#EnableConfigurationProperties can be used to include
#ConfigurationProperties beans. from Spring Boot Documentation
Your Tracer won't be part of this TestContext out-of-the-box.
For your #WebFluxTest you can provide a mocked version of this bean with #MockBean:
#WebFluxTest(controllers = TestController.class)
public MyTest {
#Autowired
private WebTestClient webClient;
#MockBean
private Tracer tracer;
#Test
public void testTrace() {
webClient.get().uri("/trace")...
}
}
... and if you want to test the full integration I would rather use #SpringBootTest.

How do I resolve this bean defninition override?

I've upgraded from Spring Boot 1.5 to Spring Boot 2.1.8. I had some tests that were working but are now failing.
I also was using maven-surefire plugin at version 2.9 and it worked, but I upgraded that to 2.22.0 as well, if that matters.
#ExtendWith(SpringExtension.class)
#WebMvcTest(value = ElementController.class, secure = false)
#ContextConfiguration(classes = TestSite1Config.class)
public class ElementControllerSite1IT {
#Autowired
protected MockMvc mvc;
#MockBean
ElementService elementService;
#BeforeEach
public void setup() {
when(elementService.getElementTable( ... )) //skipping args for brevity
.thenReturn(new ElementTable());
}
#Configuration
public static class TestSite1Config {
#Bean
#Autowired
public ElementController elementController(final ElementService elementService) {
return new ElementController(elementService, new ElementControllerProperties(DeploymentLocation.SITE1));
}
#Test
public void failSite1ValidationWithoutId() throws Exception {
ElementParameters params = getParams(false);
mvc.perform(post("/element")
.contentType(JSON)
.andExpect(status().isBadRequest());
}
//more tests, but doesn't matter.
}
There's another class like above, but replace Site1 with Site2.
There is an ElementController & Service class as well.
I get this exception:
Caused by BeanDefinitionOverrideException: Invalid bean definition with name 'elementController' defined in class path resource [ui/v2/web/ElementControllerSite1IT$TestSite1Config.class]: Cannot register bean definition [Root bean: class [null]; ... defined in class path resource [ui/v2/web/ElementControllerSite1ITConfig.class] for bean 'elementController': There is already [Generic bean: class [ui.v2.web.ElementController]; .. defined in file [...ui/v2/web/ElementController.class]] bound.
I didn't write the tests, it's code that I've inherited, in a code base that I'm just getting spooled up on.
You could try #TestPropertySource(properties ="..." :
#ExtendWith(SpringExtension.class)
#WebMvcTest(value = ElementController.class, secure = false)
#ContextConfiguration(classes = TestSite1Config.class)
#TestPropertySource(properties = {"spring.main.allow-bean-definition-overriding=true", "local.server.port=7777"})
public class ElementControllerSite1IT {
...
}
Add spring.main.allow-bean-definition-overriding=true to application.properties
Got it working with this: (for anyone who stumbles upon this question)
#ExtendWith(SpringExtension.class)
#AutoConfigureMockMvc
#WebMvcTest
#ContextConfiguration(classes = {ElementController.class,TestSite1Config.class})
public class ElementControllerSite1IT {
#Autowired
private MockMvc mvc;
...
#Configruation
public static class TestSite1Config {
#Bean
#Primary
public ElementControllerProperties elementControllerProperties() { return ... }
}
...
}

Geting java.lang.IllegalStateException: Duplicate mock definition while using #MockBean in test case

I have one service class that I want to mock but while running the test I am Getting Caused by: java.lang.IllegalStateException: Duplicate mock definition [MockDefinition#482ba4b1 name = '', typeToMock = com.service.ThirdPartyService, extraInterfaces = set[[empty]], answer = RETURNS_DEFAULTS, serializable = false, reset = AFTER]
I have tried to create mock service using #MockBean at class level, field level, and used #Qualifier as well to resolve the issue
#Service
public class ThirdPartyService{
.......................
public String decrypt(String encryptedText) {
//third party SDK I am using
return Service.decrypt.apply(encryptedText);
}
.........
..............
}
#ComponentScan("com")
#PropertySource({"classpath:/api.properties", "classpath:/common.properties"})
#SpringBootConfiguration
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {Application.class}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#Transactional
public class TestControllerTest extends IntegrationTest {
#MockBean
ThirdPartyService thirdPartyService;
#Before
public void initMocks(){
MockitoAnnotations.initMocks(this);
}
#Test
public void test() throws Exception {
when(ts.decrypt("encryptedText")).thenReturn("decryptedText")
Request req = Request.builder().name("name123").build();
//written performPost method in some other class
ResultActions action = performPost("/test", req);
action.andExpect(status().isOk());
}
}
public class IntegrationTest {
protected final Gson mapper = new Gson();
private MockMvc mvc;
#Autowired
private WebApplicationContext context;
public ObjectMapper objectMapper = new ObjectMapper();
#Before
public void setup() {
this.mvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build();
}}
When I am calling Thirdparty service decrypt method then it should return me decryptedText as a string. But getting duplicate mock definition error
I had the same issue.
Cause of this were test configuration file which was put somewhere else and it contained the mocked bean.
I have solved this by using #Autowired instead of #MockBean as this will result in autowiring the already mocked bean.
In my case the problem appeared after another dependency update and the reason was in the #SpringBootTest annotation referencing the same class twice:
#SpringBootTest(classes = {MyApplication.class, ApiControllerIT.class})
class ApiControllerIT extends IntegrationTestConfigurer {
// ...
}
#SpringBootTest(classes = {MyApplication.class, TestRestTemplateConfiguration.class})
public class IntegrationTestConfigurer {
// ...
}
I fixed it by removing #SpringBootTest annotation from the child class (ApiControllerIT).
In my case it was incorrect test class name that doesn't end with 'Test'.
If you have nested test classes try this:
#NestedTestConfiguration(OVERRIDE)
From Spring release notes: https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-5.x#upgrading-to-version-53

Spock How to mock Autowired class' function call within a method

I have a class that I want to test in that looks like this:
package com.something;
import org.springframework.beans.factory.annotation.Autowired;
public class ClassToTest implements InterfaceToTest{
#Autowired
AnotherService serviceA;
#Override
public List<String> methodToTest(List<String> randomVar){
...
String stringA = serviceA.someFunction(randomVar);
...
}
}
How can I mock the results from the call to serviceA.someFunction(randomVar) to return any String of my choice when testing with spock?
package com.something;
import spock.lang.Shared
import spock.lang.Specification
class TestClass extends Specification{
#Shared InterfaceToTest classToTest = new ClassToTest()
static doWithSpring = {
serviceA(AnotherService)
}
def "tests-part-1"(){
when: "something"
...
then: "expect this"
...
}
}
I dont know where to go from here. My IDE shows errors with the doWithSpring code I added to the testing class. Any ideas on how to deal with this?
I would suggest thinking of it from more of a unit testing perspective. You want to mock out the spring framework stuff and just make sure that you're testing your logic. This is very easy to do with Spock.
ClassToTest myClass = new ClassToTest(serviceA: Mock(AnotherService))
def "test my method"() {
when:
myClass.methodToTest([])
then:
1 * myClass.serviceA.someFunction([]) >> 'A string'
}
From here, you can look at data driving it or using a >>> and passing a list of the different strings that you'd like to return.
A simple solution to enable unit testing would be to alter ClassToTest to have a constructor which sets the serviceA field like this:
import org.springframework.beans.factory.annotation.Autowired;
public class ClassToTest implements InterfaceToTest{
private AnotherService serviceA;
#Autowired
public ClassToTest(final AnotherService serviceA){
this.serviceA = serviceA;
}
#Override
public List<String> methodToTest(List<String> randomVar){
...
String stringA = serviceA.someFunction(randomVar);
...
}
}
Then in your spock unit test you can provide a mock in the constructor:
class TestClass extends Specification{
def mockServiceA = Mock(AnotherService)
#Shared InterfaceToTest classToTest = new ClassToTest(mockServiceA)
And in each test case you can mock in the usual spock way:
1 * mockServiceA.someFunction(_) >> 'A string'
If you are unit testing, then do what #rockympls suggests.
If you are integration/component testing, then include spock-spring dependency and look at the test examples from the Spock guys. Furthermore, if you are using Spring Boot 1.4+, you can do something like:
#SpringBootTest(classes = Application)
#ContextConfiguration
class SomeIntegrationTest extends Specification {
#Autowired
SomeService someService
def 'some test case'() {
...
}
}
For more on the Spring Boot testing stuff see this.
I had the same issue and i fixed like this:
class Service {
#Autowired MyRepository myRepository
#Autowired AnotherRepository anotherRepository
}
#SpringBootTest (classes = Service)
ServiceTest extends Specification{
Service service
MyRepository myRepository
AnotherRepository anotherRepository
def setup{
myRepository = Mock(MyRepository)
anotherRepository = Mock(AnotherRepository)
service = new Service()
service.myRepository = myRepository
service.anotherRepository = anotherRepository
}
}

Resources