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

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

Related

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

How to get instances from dependency-injection container in unit tests?

Let's say I have the following class:
import com.fasterxml.jackson.databind.ObjectMapper
class Foo(private val jsonMapper: ObjectMapper) {
// ...
}
And the corresponding test:
import com.fasterxml.jackson.databind.ObjectMapper
#RunWith(MockitoJUnitRunner::class)
class FooTest {
private val jsonMapper = ObjectMapper().findAndRegisterModules()
private lateinit var foo: Foo
#Before
fun makeFoo() {
foo = Foo(jsonMapper)
}
}
My issue with this is that I have to call findAndRegisterModules (to have jsr310 support etc.) manually. I'd prefer to let Spring Boot decide how to construct my dependencies.
But the following fails because of java.lang.Exception: Test class should have exactly one public zero-argument constructor:
import com.fasterxml.jackson.databind.ObjectMapper
#RunWith(MockitoJUnitRunner::class)
class FooTest(private val jsonMapper: ObjectMapper) {
private val foo = Foo(jsonMapper)
}
So, what is the correct way to handle such a situation?
Your test fails because JUnit4 tests need to have a no args constructor.
Also your test is not using Spring as you are using the #RunWith(MockitoJUnitRunner::class) annotation. This runner allows to initialize your Mock objects and inject them into your test subject (Javadoc).
If you want to use Spring to construct your test subjects and its dependencies you need to use a different set of anntotations (check this tutorial for more details):
#RunWith(SpringRunner::class)
#SpringBootTest
class FooTest {
#Autowired
lateinit var foo: Foo
}

MockBean stubbing ineffective

I have a configuration class with a few MockBeans replacing actual beans in context for tests.
#Configuration
public class MyTestConfig {
#MockBean
private MyService myService;
}
I use those mocks in my tests:
#Import({ MyTestConfig .class })
public class MyTest {
#Autowired
private MyService myService;
#Test
public void aTest() {
...
}
}
First the idea was to add the stubbing in this MyTestConfig configuration class, so that the mock is pre-made for all tests, so I did it in a #PostConstruct method, and it worked just fine - the mock in test did return the expected value:
#PostConstruct
public void init() {
when(myService.foo("say hello")).thenReturn("Hello world");
}
It turned out though, that constructing a pre-made mock suitable for all test can be tricky, so we decided to move the stubbing to tests.
#Test
public void aTest() {
when(myService.foo("say hello")).thenReturn("Hello world");
}
And this doesn't work - the stubbed method returns null. We want to leave MockBeans in the configuration class, but stub them in tests, so any advice on why the stubbing is ineffective?
Spring Boot 2.0.5, Mockito 2.22.0
Yes, stubbing should be performed inside their respective test cases (unless you have a test class that shares the stubbing scenarios but it all comes down to preference).
However, for creating #MockBeans, you would need to use a #SpringBootTest in order to get the actual beans replaced with mocks. This could be done as simply as this example:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyTest {
#Autowired
private MyTestClass testClass;
#MockBean
private MyService service;
#Test
public void myTest() {
// testing....
}
}

SpringBootTest: Getting java.lang.NullPointerException when calling CRUD repository in #component test file

I am a beginner in testing and now I have a problem that I cannot overcome.
#SpringBootTest
ExampleMakerSpec extends Specification {
#Autowired #Subject ExampleMaker exampleMaker
#Autowired
ExampleRepository exampleRepository
def EXAMPLE_VARIABLE = "Example"
#Transactional
def "example() trying to do somehthing" () {
when: "trying to make some examples"
Examples examples = exampleMaker.createExamples(examples)
then: "get examples sizes and saves them to database"
examples.size == 7
My Example maker looks like that:
#Component
public class ExampleMaker {
#Autowired
ExampleRepository exampleRepository;
public void createExamples() {
exampleRepository.save(Examples);
}
}
And CRUD repository:
#Repository
public interface exampleRepository extends CrudRepository<Example, Long> {
}
but I am always getting
java.lang.NullPointerException at line exampleRepository.save(Examples).
So for some reason the tests cannot find the repository. I need assistance here to understand what is missing.
You need to autowire the exampleRepository in your test class. So when your test class ExampleMakerSpec is created, Spring looks for and injects ExampleRepository

Mock Spring service in controller test using Spock

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())
}
}

Resources