I have spring boot application with kotlin, and the thing is: I can't mock 3rd party final class only when #Retryable is used. Here is my project:
#Component
class RestClient {
fun getAllData() : List<String> = listOf("1")
}
#Service
class MyService(val restClient: RestClient) {
#Retryable(maxAttempts = 3)
fun makeRestCall() : List<String> {
return restClient.getAllData()
}
}
So I would like to test two cases:
when RestClient throws HttpClientErrorException.NotFound exception,
makeRestCall() should return null (that is not supported by
current implementation by that does not matter)
when makeRestCall() is invoked - I would like to check with mockito that it is really invoked (no sense, but why can't I do it? )
Here is my test:
#EnableRetry
#RunWith(SpringRunner::class)
class TTest {
#MockBean
lateinit var restClient: RestClient
#SpyBean
lateinit var myService: MyService
#Test
fun `should throw exception`() {
val notFoundException = mock<HttpClientErrorException.NotFound>()
whenever(restClient.getAllData()).thenThrow(notFoundException)
}
#Test
fun `method should be invoked`() {
myService.makeRestCall()
verify(myService).makeRestCall()
}
}
In order to mock final class org.springframework.web.client.HttpClientErrorException.NotFound, I've added mockito-inline as dependency
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.1.0</version>
<scope>test</scope>
</dependency>
So, here is the problem:
when I run my tests with mockito-inline as dependency:
test should throw exception - passes
test method should be invoked - fails with exception :Argument passed to verify() is of type MyService$$EnhancerBySpringCGLIB$$222fb0be and is not a mock!
when I run my tests without mockito-inline as dependency:
test should throw exception - fails with exception: Mockito cannot mock/spy because : final class
test `method should be invoked - passes
when I run my tests without #EnableRetry - both tests passes, but I'm not able to test retry functionality
How can I deal with that?
Related
I am trying to write unit tests for a Spring boot service using JUnit 4 and Mockito.
I used constructor based dependency injection for my service, The Signature is:
class VbServiceImp(val jdbcTemplate: NamedParameterJdbcTemplate,
val nuanceService: NuanceService,
val conf: AppConfigProps,
val eventService: EventServiceImp,
val audioTrimService: AudioTrimServiceIF,
val vbNuanceStagingDeletionsService: VbNuanceStagingDeletionsService) : VbService {...}
in another part of the application This service gets injected into a controller and somehow spring just magically knows what to inject without me specifying this (Any idea how this works/explanation would be appreciated, guess it's based on component scan?)
example:
class VbController(val vbService: VbService) {...}
Now in my VBServiceImpl Unit test class I try to mock all the above dependencies before declaring vBService in order to manually inject all dependencies into VBService during declaration.
the relevant part of my test class looks like this:
#RunWith(SpringRunner::class)
#ContextConfiguration()
class VBServiceTests {
#MockBean
val jdbcTemplate: NamedParameterJdbcTemplate = mock()
#MockBean
val nuanceService: NuanceService = mock()
#MockBean
val appconfigProps: AppConfigProps = AppConfigProps()
#MockBean
val eventService: EventServiceImp = mock()
#MockBean
val audioTrimService: AudioTrimService = mock()
#MockBean
val vbNuanceStagingDeletionsService: VbNuanceStagingDeletionsService = mock()
val vbService: VbServiceImp = VbServiceImp(jdbcTemplate, nuanceService, appconfigProps, eventService, audioTrimService, vbNuanceStagingDeletionsService)
#SpyBean
val vbServiceSpy: VbServiceImp = Mockito.spy(vbService)
#Before
fun setup() {
initMocks(this)
}
When I run a test I get the exception below. If I understand this correctly there is already a bean of type jdbcTemplate in the application context and therefore I can't define the #Mockbean jdbcTemplate above?
exception:
private final org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate com.cc.ff.vb.service.VBServiceTests.jdbcTemplate cannot have an existing value
So now the issue is: If I removed the #MockBean jdbcTemplate variable then I can't inject jdbcTemplate when I declare vbService in my test class. So how could I get around this/make this work?
Just to check I removed the jdbcTemplate parameter from the vbService class constructor and changed it to a #Autowired field injected class variable and provided the mock class using #TestConfig. This worked however then the exception popped up on the next constructor parameter (NuanceService)
i'm out of ideas and google hasn't returned anything of value. Do I remove all constructor injected dependencies and then make them field injected using #Autowired and then provide the beans in the nested #TestConfig annotated class or is there a better/cleaner way? AFAIK field based injection is supposed to be bad practice?
example of providing correct bean for testing #Autowired field injected jdbcTemplate variable:
#TestConfiguration
class testConfig {
#Bean
fun jdbcTemplate(): NamedParameterJdbcTemplate {
return mock<NamedParameterJdbcTemplate>()
}
}
This is how I ended up making it work:
#TestConfiguration
class testConfig {
#Bean
fun jdbcTemplate(): NamedParameterJdbcTemplate {
return mock<NamedParameterJdbcTemplate>()
}
#Bean
fun nuanceService(): NuanceService {
return mock<NuanceService>()
}
#Bean
fun appConfigProps(): AppConfigProps {
return mock<AppConfigProps>()
}
#Bean
fun eventService(): EventServiceImp {
return mock<EventServiceImp>()
}
#Bean
fun audioTrimService(): AudioTrimService {
return mock<AudioTrimService>()
}
#Bean
fun vbNuanceStagingDeletionService(): VbNuanceStagingDeletionsService {
return mock<VbNuanceStagingDeletionsService>()
}
}
#MockBean
lateinit var nuanceService: NuanceService
#SpyBean
lateinit var vbServiceSpy: VbServiceImp
I'm still not sure if this is the best/optimal way of going about this so would appreciate some more details...
How to Mock mockRepository.deleteById() using mockito in spring boot?
It depends on where you want to use this mock. For integration tests running with the SpringRunner it is possible to annotate the repository to mock with the MockBean annotation. This mocked bean will be automatically inserted in your context:
#RunWith(SpringRunner.class)
public class SampleIT {
#MockBean
SampleRepository mockRepository;
#Test
public void test() {
// ... execute test logic
// Then
verify(mockRepository).deleteById(any()); // check that the method was called
}
}
For unit tests you can use the MockitoJUnitRunner runner and the Mock annotation:
#RunWith(MockitoJUnitRunner.class)
public class SampleTest {
#Mock
SampleRepository mockRepository;
#Test
public void test() {
// ... execute the test logic
// Then
verify(mockRepository).deleteById(any()); // check that the method was called
}
}
The deleteById method returns void, so it should be enough to add the mock annotation and check if the mocked method was called (if needed).
You can find more info here
I have spring controller handler. I have to test that handle by using Junit test case. I am new to Juint so not able to test Junit. I want to run this in eclipse.
spring handler:
#Controller
#RequestMapping("/core")
public class HelloController {
#RequestMapping(value = "/getEntityType", method = RequestMethod.GET)
public ResponseEntity<List<MyEnum >> getEntityType(HttpServletRequest
request, HttpServletResponse response) {
return new ResponseEntity<List<MyEnum >>(Arrays.asList(MyEnum.values()), HttpStatus.OK);
}
Enum Class:
public enum MyEnum {
FIRST, SECOND, THIRD;
}
TestCase:
#Test
public void testToFindEnumTypes() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "core/getEntityType");
MockHttpServletResponse response = new MockHttpServletResponse();
hello.Controller.getEntityType(request, response);
Assert.assertNotNull(getResponseJSON(response));
}
Please tell me how to Run Junit Test case for that handler. I am new to Junit testing.
From Eclipse, there should be a green run button that allows you to run JUnit tests.
Eclipse Help has this really good article explaining how to:
https://help.eclipse.org/neon/index.jsptopic=%2Forg.eclipse.jdt.doc.user%2FgettingStarted%2Fqs-junit.htm
Also, if running outside your Eclipse, and doing it in a maven build, you need to add junit dependency to your maven pom.xml file:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
Then run maven test command as follows:
mvn clean test
Also, there a few syntax errors in your posted code. It should be like something like this:
public class HelloControllerTest {
private HelloController helloController = new HelloController();
#Test
public void testToFindEnumTypes() throws Exception {
// setup
MockHttpServletRequest request = new MockHttpServletRequest("GET", "core/getEntityType");
MockHttpServletResponse response = new MockHttpServletResponse();
// execution
ResponseEntity actualResponse = helloController.getEntityType(request, response);
// verify
assertNotNull(actualResponse);
assertEquals(HttpStatus.OK, actualResponse.getStatusCode());
List<MyEnum> myEnumList = (List<MyEnum>) actualResponse.getBody();
assertTrue(myEnumList.contains(MyEnum.FIRST));
assertTrue(myEnumList.contains(MyEnum.SECOND));
assertTrue(myEnumList.contains(MyEnum.THIRD));
}
Ideally, you should also verify correctly all the return values like i done in the example above.
Using Spring boot starter test for testing my application but I am using third party library. Lets suppose we have a class TRequest and it has some constructor and I want to mock and stub that constructor to return the result.
#SpringBootTest
#RunWith(SpringRunner.class)
#PrepareForEverythingForTest
public class TestClass {
#MockBean
TRequest trequest ;
#Before
public void setUp() throws Exception {
PowerMockito.whenNew(TRequest.class).withAnyArguments().thenReturn(trequest);
}
}
Now when I am trying to create the constructor using new, it is not returning the correct stubbed result.
TRequest trequest1 = new TRequest("apiKey","secretKey") ;
trequest.equals(trequest1) ; // false but I want it to be true
Have used a jackson third party lib to test with. - getting ClassLoader exceptions because of PowerMock though.
#SpringBootTest
#RunWith(PowerMockRunner.class)
#PowerMockRunnerDelegate(SpringRunner.class)
public class TestPowerMockito {
#MockBean
ObjectMapper object;
#Before
public void init() throws Exception {
PowerMockito.whenNew(ObjectMapper.class).withAnyArguments().thenReturn(object);
}
#Test
public void test() {
assertEquals(object, new ObjectMapper());
}
}
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())
}
}