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

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
}

Related

spring boot startup process failed with lateinit var not initialized : DI Issue

I'm buidling a backend that will fetch some data from several externals APIs, to populate DB after some processes, and then expose these data via a rest api. Using JPA and postgresql, in springboot.
I have created the entities, repositories and fetch external apis from a webclient.
However, I have a dependency injection issue. I read tons of articles and issues, but can not make it work. When trying to inject the repository, I got the well known error lateinit var not initialized.
I also tried constructor injection, but still doesn't work. It seems that the repository is not considered to be a bean that could be autowired.
Any help would be appreciated
FIXED SEE SOLUTION BELOW. PB WAS IN THE SERVICE
Application. Running the startuprocess from the context
#SpringBootApplication(exclude = [JacksonAutoConfiguration::class])
class SpringbootkotlinApplication
fun main(args: Array<String>) {
val context = runApplication<SpringbootkotlinApplication>(*args)
val startupProcess = context.getBean(StartupProcesses::class.java)
startupProcess.fetchGroup()
}
startup class as #component :fetches external api and calls a service to save data in db
#Component
class StartupProcesses {
fun fetchGroup() {
val fetch = IronMaidenSourceApi.fetchIronMaidenSource() //<-- fetch ext api OK
SplitDataSourceToEntities().populateGroup(fetch) //<-- call service to populate db
}
}
Service that should populates the DB through repository but error in lateinit repo
#Service
class SplitDataSourceToEntities {
#Autowired
lateinit var repo:IronMaidenGroupRepository // <-- error is here
fun populateGroup(dto: DTOIronMaidenAPi): IronMaidenGroupEntity {
val dbgroup = IronMaidenGroupEntity(
groupId = dto.id!!,
name = dto.name ?: ""
)
return repo.save(dbgroup)
}
}
the repo, extending JPA repository
import com.jerome.springbootkotlin.model.dbentities.ironmaidengroupentity.IronMaidenGroupEntity
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
#Repository
interface IronMaidenGroupRepository:JpaRepository<IronMaidenGroupEntity, String> {
}
SOLUTION
Service should be defined like this (the SplitDataSourceToEntitiesclass should also be late init instead of beeing instantiated)
#Component
class StartupProcesses {
#Autowired
lateinit var splitDataSourceToEntities: SplitDataSourceToEntities // <--add this lateinit
fun fetchGroup() {
val fetch = IronMaidenSourceApi.fetchIronMaidenSource()
splitDataSourceToEntities.populateGroup(fetch) //<-- fix is here
}
}
#Autowired works only when managed by Spring
It is important to note, that #Autowired does only work when the class has been initialized by Spring. In your code, you have an #Autowired annotation in class SplitDataSourceToEntities, but you manually instantiate SplitDataSourceToEntities in StartupProcesses.fetchGroup. That cannot work, because Spring had no possibility to auto-wire the lateinit var.
The problem can easily be solved by using an autowired instance of your service:
#Component
class StartupProcesses {
#Autowired
lateinit var splitDataSourceToEntities: SplitDataSourceToEntities
fun fetchGroup() {
val fetch = IronMaidenSourceApi.fetchIronMaidenSource() //<-- fetch ext api OK
splitDataSourceToEntities.populateGroup(fetch) //<-- call service to populate db
}
}
Help Spring finding your JpaRepository
Additionally, you probably need to add an #EnableJpaRepositories annotation to your application:
#EnableJpaRepositories(basePackageClasses = [IronMaidenGroupRepository::class])
#SpringBootApplication(exclude = [JacksonAutoConfiguration::class])
class SpringbootkotlinApplication
...
Instead of basePackageClasses = ... you can just define the package directly by name, but the package name is not included in your example.
Do you have to use #Autowired at all?
Considering your question in the comments:
There is nothing wrong with your design, because it is not not necessary to "play" with #Component and #Autowired. You could as well get rid of the #Autowired annotations and define those variables as, e.g., constructor parameters.
That could look like this:
class SpringbootkotlinApplication
fun main(args: Array<String>) {
val context = runApplication<SpringbootkotlinApplication>(*args)
val repo = context.getBean(IronMaidenGroupRepository::class.java)
StartupProcesses(SplitDataSourceToEntities(repo))
}
#Component
class StartupProcesses(
val splitDataSourceToEntities: SplitDataSourceToEntities
) {
...
}
#Service
class SplitDataSourceToEntities(
val repo: IronMaidenGroupRepository
) {
...
}
Now only your Repository is managed by Spring, but on the flip-side you have to manage everything else by yourself which might get very tedious when your classes and their dependencies grow. It is much more comfortable (and in the end leads to better readable code) to let just Spring manage all the dependencies.

How to inject a list of implementations in a #InjectMockKs test instance?

Spring Boot allows to inject a list of all implementations of an interface (SomeComponent) as List into another component (SomeOtherComponent), e.g.
#Component
interface SomeComponent
#Component
class SomeComponentImpl0 : SomeComponent
#Component
class SomeComponentImpl1 : SomeComponent
class SomeOtherComponent {
#Autowired
lateinit var impls: List<SomeComponent>
}
How can I inject mocks for the implementations using MockK annotations? In
import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.MockK
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class SomeOtherComponentTest {
#MockK
lateinit var someComponentImpl0: SomeComponentImpl0
#MockK
lateinit var someComponentImpl1: SomeComponentImpl1
#InjectMockKs
lateinit var instance: SomeOtherComponent
#BeforeEach
fun setup() {
MockKAnnotations.init(this)
}
#Test
fun testSomething() {
println(instance.impls.toString())
}
}
I'm getting either
io.mockk.MockKException:
No matching constructors found:
constructor(impls : kotlin.collections.List<de.richtercloud.inject.mocks.foor.list.of.impl.SomeComponent> = <not able to lookup>)
at de.richtercloud.inject.mocks.foor.list.of.impl.SomeOtherComponentTest.setup(SomeOtherComponentTest.kt:40)
if I'm using constructor injecction and
kotlin.UninitializedPropertyAccessException: lateinit property impls has not been initialized
at de.richtercloud.inject.mocks.foor.list.of.impl.SomeOtherComponentTest.testSomething(SomeOtherComponentTest.kt:26)
if I'm using an #Autowired var property in the class.
I'm using 1.3.50 through Maven 3.6 and MockK 1.9.3.
Add #ExtendWith(MockKExtension::class) above your Test class to make #InjectMokKs work.
I had the same problem here. It worked when I removed lateinit from the component being tested, and instantiated it on declaration.
For example, changed:
#InjectMockKs
lateinit var instance: SomeOtherComponent
to:
#InjectMockKs
var instance = SomeOtherComponent()

#SpyBean not working with Pact and JUnit 5

I'm trying to use the #SpyBean to mock a method of a #Component and doesn't work. #MockBean works (followed the example). I've tried, read and researched many ways but couldn't make it work.
Here's the example:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment. DEFINED_PORT)
#ExtendWith(SpringExtension::class)
#Provider("MyMicroService")
#PactFolder("../../../pacts")
internal class ClientContracts {
#SpyBean
private lateinit var myService: MyService
#TestTemplate
#ExtendWith(PactVerificationInvocationContextProvider::class)
fun pactVerificationTestTemplate(context: PactVerificationContext) {
context.verifyInteraction()
}
#State("default", "NO_DATA")
fun toDefaultState() {
reset(processService)
}
}
(I super simplified the test function so it's easier to read, I'd be actually doing doReturn(...).when(...).blah())
I'm always getting the "not a mock" error, because the object is always the bean wrapped by Spring CGLIB:
org.mockito.exceptions.misusing.NotAMockException: Argument should be a mock, but is: class com.blah.MyServiceImpl$$EnhancerBySpringCGLIB$$9712a2a5
at com.nhaarman.mockitokotlin2.MockitoKt.reset(Mockito.kt:36)
...
I've tried:
with #SpringJUnitConfig
with a separate #TestConfiguration, but got resolved to same above bean
Using Mockito.initAnnotations(this) in a #BeforeEach
and more, I've tried with so many combinations that I can't remember...
Is there something that I'm missing? Or an option that I don't know?
Above issue is not related to the pact or pact JVM library
The issue is not about spring
Spring - I use spring with mockito and it works, the simple example is:
import com.nhaarman.mockito_kotlin.doReturn
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.mock.mockito.SpyBean
import org.springframework.test.context.junit.jupiter.SpringExtension
#ExtendWith(value = [SpringExtension::class])
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = [Application::class]
)
internal class processorIntegrationTest : IntegrationTest() {
#SpyBean
// #MockBean
private lateinit var processor: Processor;
#Test
internal fun abcd() {
doReturn("something").`when`(processor).get()
val get = processor.get()
assertThat(get).isEqualTo("something")
}
}
Mockito - mockito_kotlin or mockito extension works with SpyBean
Issue is about mockito + CGLIB
CGLIB - from your logs feels like class com.blah.MyServiceImpl$$EnhancerBySpringCGLIB$$9712a2a5 there is a wrapper on top of your service implementation which is SpyBean.
Which means CGLIB wrapper is not and the error is for that.
Try removing CGLIB wrapper and it will work

Spring Boot testing with different service class

I have a pretty basic question, apologies if it has been asked before. I fear I may not be using the right words, this is my first rodeo with Spring.
I have a RestController declared as such:
#RestController
class TelemetryController {
#Autowired
lateinit var service: TelemetryService
//...
}
with a concrete implementation of TelemetryService as such in our main module:
#Service
class ConcreteTelemetryService : TelemetryService {
// some production code
}
I then have a service I want to use in my controller during tests (inside our test module:
#Service
class TestingTelemetryService : TelemetryService {
// some test code using local data
}
Critically, I have do NOT want to use Mockito for this, as the implementation of the tests require very specific setup that is not appropriate for Mockito.
My test is declared as such:
#RunWith(SpringRunner::class)
#SpringBootTest
#AutoConfigureMockMvc
class HowDoInjectServiceExampleTest {
#Autowired
lateinit var mockMvc: MockMvc
}
How do I get my TestingTelemetryService inside my controller in this instance?
There are various way to achieve this but I would recommend to use Spring Profiles.
Use the default profile with the concrete implementation. This bean will be used if no profile is specified.
#Profile("default")
#Service
class ConcreteTelemetryService : TelemetryService {
// some production code
}
Add the profile "test" to the test implementation.
#Profile("test)
#Service
class TestingTelemetryService : TelemetryService {
// some test code using local data
}
Now you can start your test with
-Dspring.profiles.active=test
Read more about profiles here:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-profiles.html
If your TestingTelemetryService is in same package as HowDoInjectServiceExampleTest then you can simply autowire test bean like
#RunWith(SpringRunner::class)
#SpringBootTest
#AutoConfigureMockMvc
class HowDoInjectServiceExampleTest {
#Autowired
lateinit var mockMvc: MockMvc
#Autowired
var service: TestingTelemetryService
}
if not then you should define some TestConfiguration and programatically define bean with service name and use it using #Qualifier in test to resolve which bean to use (in your case its test bean)

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