Testing Kotlin Extension Functions with Spring - spring

I have a controller that gets a specific service based on a customer name:
#RestController
class BasicController(){
#Autowired
private lateinit var services: List<BasicService<*>>
private var service: BasicService<*>? = null
#GetMapping("/{customer}")
fun getAll(#PathVariable customer: String): ResponseEntity<String>{
service = services.getServiceByCustomer(customer)
/... code w/return value .../
}
}
I have a file Extensions.kt with the following:
fun <T: BasicService> List<T>.getServiceByCustomer(customer: String): T?{
return this.find{
it::class.simpleName?.contains(customer, ignoreCase = true) == true
}
}
Is it possible to return a mock of service when services.getServiceByCustomer is called similar to `when`(mock.function(anyString())).thenReturn(value)?
I've tried using mockK with the following:
mockkStatic("path.to.ExtensionsKt")
every {listOf(service).getServiceByCustomer)} returns service
But I don't think I'm using that properly... I'm currently using com.nhaarman.mockitokotlin2 but have tried io.mockk

You just need to use a customer that actually matches the simple name of the mocked service. You don't need or even should mock the extension function. Try the following:
class BasicControllerTest {
#MockK
private lateinit var basicService: BasicService
private lateinit var basicController: BasicController
#BeforeEach
fun setUp() {
clearAllMocks()
basicController = BasicController(listOf(basicService))
}
}
Additionally, consider using constructor injection instead of field injection:
#RestController
class BasicController(private val services: List<BasicService<*>>){
private var service: BasicService<*>? = null
#GetMapping("/{customer}")
fun getAll(#PathVariable customer: String): ResponseEntity<String>{
service = services.getServiceByCustomer(customer)
/... code w/return value .../
}
}
Finally, consider testing the Controllers with #WebMvcTest instead of regular unit tests. Check more info here https://www.baeldung.com/spring-boot-testing#unit-testing-with-webmvctest.

Related

spring junit test data preparrer class access repositories trough field

I have simple class annotated with #Component and injected repositories like
#Component
class TestsDataPreparer(
private val carRepository: CarRepository
) {
fun withCar(builder: Car.() -> Unit = {}): Car {
return carRepository.save(
Car(
name = builder.name!!
)
)
}
}
which is clear..
But i wonder if it would be ok to do something like this, or if it is considered as anti-pattern.
#Component
class TestsDataPreparer(
val carRepository: CarRepository
) {
fun withCar(builder: Car.() -> Unit = {}): Car {
return carRepository.save(
Car(
name = builder.name!!
)
)
}
}
#Test
fun testCar() {
testsDataPreparer.withCar{this.name="Super-cool-car!"}
assertThat(testsDataPreparer.carRepository.findAll()).hasSize(1)
}
So the question is if it is okay to not inject repository in test class itself, but reuse it from TestsDataPreparer class
Yes, making an originally private field public just for testing can be considered an antipattern. Instead, you can create a CarRepository instance and then pass it to TestsDataPreparer when you create it. But for unit testing, you don't actually need that, you can use a mock and verify that the correct method was called (CarRepository.save).

How to interpolate property values provided by custom PropertySource in Spring Boot?

I have my custom FooPropertySources that extends EnumerablePropertySource. I add all of these in the #Configuration class to the ConfigurableEnvironment and they are correctly picked up be application and all the values are resolved.
However, if some values contain placeholders, they're not being interpolated. I thought I should use PropertySourcesPlaceholderConfigurer to solve that problem, but it seems like this configurer is meant to deal with placeholders in beans, rather than in property sources.
So far I tried this:
#Configuration
#ConditionalOnProperty("foo.config.import")
open class FooConfiguration {
#Autowired
private lateinit var env: ConfigurableEnvironment;
#Value("\${foo.config.import}")
private lateinit var locationSpecifier: String;
#PostConstruct
private fun initialize() {
val placeholderConfigurer = PropertySourcePlaceholderConfigurer();
val beanFactory = DefaultListableBeanFactory();
this.resolvePropertySources(this.parseLocationSpecifier())
.forEach(this.env.propertySources::addFirst);
placeholderConfigurer.setEnvironment(this.env);
placeholderConfigurer.postProcessBeanFactory(beanFactory);
}
internal fun resolvePropertySources(path: Path): Set<FooPropertySource> {
//...
return ...;
}
internal fun parseLocationSpecifier(): Path {
//...
return path;
}
}
Now, if an instance of FooPropertySource contains these properties:
firstname = John
lastname = Doe
fullname = ${firstname} ${lastname}
I'd like, in the end, when my application calls to env.getProperty("fullname") it will get the string "John Doe", rather than "${firstname} ${lastname}".
Any hopes to resolve that problem? I'm struggling with it for third day already… :-(
I guess you could create an extension function
fun ConfigurableEnvironment.fullname() = "${getProperty("firstname")} ${getProperty("lastname")}"

How to mock beans Autowired on service layer in #WebMvcTest

I am testing a REST API's in Spring boot gradle app, my mocked service using #MockBean is returning null. This mocked service return null if there are some beans Autowired in service class(I used constructor injection).
Here is sample Code(Not compiled, only for understanding)
#RestController
#RequestMapping("/xxx")
class TestController {
private RetriveDataService retriveDataService;
public TestControllerx(RetriveDataService retriveDataService) {
this.retriveDataService = retriveDataService;
}
#PostMapping(value = "/yyy")
public MyResponseModel myMethod(#RequestBody MyRequestModel model) {
return retriveDataService.retriveData(model);
}
}
#Service
class RetriveDataService {
private TokenService tokenService;
public RetriveDataService(TokenService tokenService) {
this.tokenService = tokenService;
}
public MyResponseModel retriveData(MyRequestModel model) {
String accessToken = tokenService.getToken().getAccessToken();
return retriveData(model, accessToken);
}
}
#RunWith(SpringRunner.class)
#WebMvcTest(TestController.class)
public class TestControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
private ObjectMapper objectMapper;
#MockBean
private RetriveDataService retriveDataService;
#Test
public void testRetriveData() throws Exception {
mvc.perform(MockMvcRequestBuilders.post("/xxx/yyy").content(objectMapper.writeValueAsString(new MyRequestModel()))
.contentType(MediaType.APPLICATION_JSON_UTF8)).andDo(MockMvcResultHandlers.print())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
}
}
When I run this test, i am getting following output(If my service do not need another bean, I am getting expected output)
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Due to this response i facing problem on line .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));. also when i check response body(as body is also a null)
Sample project to reproduce the issue is here
Checking your repository confirmed assumption form the discussion in comments under question.
You specify expectations on your mock
MyModel requestMessage = new MyModel();
requestMessage.setMessage("Hello Request Post");
given(testService1.getMessage(requestMessage)).willReturn(responseMessage);
but the message received to in your controller in your #WebMvcTest is not equal to requestMessage specified in the test. This is due to the fact that MyModel class does not override equals method.
In this situation, Mockito will use its default behaviour:
By default, for all methods that return a value, a mock will return either null, a primitive/primitive wrapper value, or an empty collection, as appropriate. For example 0 for an int/Integer and false for a boolean/Boolean.
You have two options to fix the problem:
override equals (and hashCode) in your request class.
Get acquainted with argument matchers
More info on option 2.:
Technically, your expectation is equivalent to:
given(testService1.getMessage(ArgumentMatchers.eq(requestMessage)))
.willReturn(responseMessage);
You can use other matcher, or even define your own. This is useful if you cannot modify code of your argument's type (type coming from 3-rd party library etc).
For example, you can use ArgumentMatchers.any(MyModel.class))

PowerMockito mock private methods in springboot

I am trying to mock a private method inside my class under test which is as below.
public String processPayment(...) {
//some lines
privateMethod(...);
return "";
}
private Object privateMethod(...) {
//some lines
return someObject;
}
Now I need to test processPayment method and mock privateMethod.
I tried creating spy of the above class, but the method gets called when I do below
final DeviceCheckoutServiceImpl spyDeviceCheckoutService = spy(injectedMockBeanOfAboveClass); //#InjectMock in test class
PowerMockito.doReturn(null).when(spyDeviceCheckoutService, "privateMethod", ArgumentMatchers.anyMap()); //method gets called here
spyDeviceCheckoutService.processPayment(...); //private method isn't mocked somehow, and gets called here too
The privateMethod gets called on the 2nd line itself.
Also, the privateMethod isn't mocked.
Maybe I am creating the spy object in a wrong way? Can't do spy(new DeviceCheckoutServiceImpl()); as it needs bean instantiation.
Powermockito version:
compile group: 'org.powermock', name: 'powermock-module-junit4', version: '2.0.0'
compile group: 'org.powermock', name: 'powermock-api-mockito2', version: '2.0.0'
Let me know what I am doing wrong here.
In the test class we will call the spy() method of org.powermock.api.mockito.PowerMockito by passing the reference to the class that needs to be tested:
MockPrivateMethodExample spy = PowerMockito.spy(mockPrivateMethodExample);
Then we define what we want to do when this particular private method is called.
PowerMockito.doReturn("Test").when(spy, {$methodName});
MockPrivateMethodExample.java
public class MockPrivateMethodExample {
public String getDetails() {
return "Mock private method example: " + iAmPrivate();
}
private String iAmPrivate() {
return new Date().toString();
}
}
MockPrivateMethodTest.java
#RunWith(PowerMockRunner.class)
#PrepareForTest(MockPrivateMethodExample.class)
public class MockPrivateMethodTest {
private MockPrivateMethodExample mockPrivateMethodExample;
// This is the name of the private method which we want to mock
private static final String METHOD = "iAmPrivate";
#Test
public void testPrivateMethod() throws Exception {
mockPrivateMethodExample = new MockPrivateMethodExample();
MockPrivateMethodExample spy = PowerMockito.spy(mockPrivateMethodExample);
PowerMockito.doReturn("Test").when(spy, METHOD);
String value = spy.getDetails();
Assert.assertEquals(value, "Mock private method example: Test");
PowerMockito.verifyPrivate(spy, Mockito.times(1)).invoke(METHOD);
}
}
More details here: https://examples.javacodegeeks.com/core-java/mockito/mockito-mock-private-method-example-with-powermock/
Issue solved!
Had forgot to add #PrepareForTest(DeviceCheckoutServiceImpl.class) on the test class.
Mockito introduced AdditionalAnswers.delegatesTo to support spying on Spring proxies and other such things:
Check out this issue: https://github.com/mockito/mockito/issues/529#issuecomment-239494581
So instead of spy(proxy) use mock(TestSubject.class,
delegatesTo(springProxy)).
However if annotations are need to read, then you'll need mockito 2
beta. Because mockito 1.x uses CGLIB which doesn't copies annotations
on the mockito subclass. Mockito 2 uses the great bytebuddy.

groovy spock mocking spring autowired beans

I have bean:
#Service
public class EQueueBookingService {
#Autowired
public EQueueBookingClient eQueueBookingClient;
And I try to write some test for this bean EQueueBookingService using Spock.
https://code.google.com/p/spock/wiki/SpockBasics
My mock is
class EQueueBookingServiceTest extends Specification {
#Autowired
EQueueBookingService testedService;
EQueueBookingClient eQueueBookingClient = Mock(EQueueBookingClient);
def setup() {
testedService.eQueueBookingClient = eQueueBookingClient;
}
and test method:
...
setup:
CancelBookingResponse response = new CancelBookingResponse();
...
eQueueBookingClient.cancelBooking(_, _) >> response;
when:
def result = testedService.cancelBooking(request);
then:
result != null && result.bookId == bookId
Why eQueueBookingClient doesn't mock?
When I debug it: in test - I see Mock instance, when I go to method - I see real bean instance.
Thanks a lot!
I've found solution!
need implement setter for this client and use it in setup like:
private EQueueBookingClient eQueueBookingClient = Mock(EQueueBookingClient);
def setup() {
testedService.setBookingClient(eQueueBookingClient);
}
If define client in service as public and use = it doesn't work;
For example:
testedService.eQueueBookingClient = eQueueBookingClient;//mocked instance doesn't work

Resources