#SpyBean not working with Pact and JUnit 5 - spring-boot

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

Related

Springboot with Graphql error with "Consider defining a bean of type '{component name}' in your configuration."

I am using Spring boot with graphql and rest.
While adding graphql component I used annotation like this.
Controller
#Controller // <------ with this annotation
#EnableAutoConfiguration
class AController(
#Autowired val aRepository: ARepository,
#Autowired val aService: AService
){
...
}
Service
#Service // <------ with this annotation
class AService (
{
...
}
Repository
#GraphQlRepository
interface ARepository: JpaRepository<A, Long> {
But got these errors
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type '{component name}' in your configuration.
It only happens when using test
#WebMvcTest
#AutoConfigureMockMvc
internal class ControllerTest {
#Test
#WithMockUser
fun healthCheck() {
mockMvc.perform(get("/api/v1/healthcheck"))
.andExpect(status().isOk).andExpect(
content().string("healthy")
).andDo(print())
}
}
I understand I can use ComponentSacn but I want to know why this happend.
Because this package was place along with other components witch #ComponentScanner works well.
My structure is like this.
material works well while it cannot scan bakeryReview
I think I used component annotations okay, and package structure is okay. 😭 Maybe test is the problem?
My bad!! 😂 There's nothing wrong with annotation nor structure.
For those who see this question, It was WebMvcTest. WebMvcTest needs component to be mocked, because it only injects web-related components like #Controller. So it omits other components like #Service or #Repository.
I added these and it works okay.
#MockBean
lateinit var aService: AService
#MockBean
lateinit var aRepository: ARepository

Spring boot v2 No qualifying bean of type TestRestTemplate

I am getting an error when trying to get the TestRestTemplate. Is there any way to get the TestRestTemplate or test the ErrorController?
error log: https://pastebin.com/XVPU9qrb
Main test class:
package io.kuark.ppalli
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT
import org.springframework.test.context.ActiveProfiles
#ActiveProfiles("test")
#SpringBootTest(classes = [PpalliApi::class], webEnvironment = RANDOM_PORT)
class PpalliApiTest {
#Test
fun contextLoads() {}
}
Unit test class:
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.web.client.TestRestTemplate
import kotlin.test.assertEquals
#WebMvcTest
class FallbackErrorControllerTest {
#Autowired
lateinit var http: TestRestTemplate
#Test
fun handleError() {
val result = http.getForObject("/error", String::class.java);
assertEquals("""{"code": "NOT_FOUND"}""", result)
}
}
When working with #WebMvcTest, Spring Boot won't start the embedded Tomcat server and you won't be able to access your endpoints over a port. Instead, Spring Boot configures a mocked servlet environment that you directly interact with using MockMvc:
#WebMvcTest
class FallbackErrorControllerTest {
#Autowired
lateinit var mockMvc: MockMvc
#Test
fun handleError() {
mockMvc.perform(get("/error")).andExpect(status().isOk);
}
}
MockMvc even comes with its own Kotlin DSL.
When using #SpringBootTest and webEnvironment=RANDOM_PORT however, Spring Boot will auto-configure Tomcat and expose your application on a random port. This will be a real servlet environment where you can use TestRestTemplate as an HTTP client to interact and test your endpoints.

Could not autowire JobLauncherTestUtils

I am attempting to test a simple spring batch application.
Using the Spring Batch documentation as a guide (found here), I have created the following test class:
import org.junit.runner.RunWith;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.batch.test.context.SpringBatchTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import static org.junit.jupiter.api.Assertions.assertNotNull;
#SpringBatchTest
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = BatchConfig.class)
class BatchConfigTest {
#Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
#Test
void userStep() {
assertNotNull(jobLauncherTestUtils, "jobLauncherTestUtils should not be null");
}
}
According to docs #SpringBatchTest should inject the JobLaucherTestUtils bean. However, when I run the test, the assertion fails. I have also tried defining the bean in an inner configuration class and had the same result:
static class TestConfiguration {
#Autowired
#Qualifier("userJob")
private Job userJob;
#Bean
public JobLauncherTestUtils jobLauncherTestUtils() {
JobLauncherTestUtils utils = new JobLauncherTestUtils();
utils.setJob(userJob);
return utils;
}
}
Is there something I'm missing? The full source code can be found here.
I am using Spring Batch v4.2.0 and JUnit 5
You are using #RunWith(SpringRunner.class) which is for JUnit 4. You need to use #ExtendWith(SpringExtension.class) for JUnit 5 tests:
#SpringBatchTest
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = BatchConfig.class)
class BatchConfigTest {
// ...
}
I had a same problem with Spring Batch 4.1.3 and JUnit 4.12.
Replacing #SpringBatchTest with #SpringBootTest solved the problem.
I was seeing the same error in IntelliJ and spent ages looking into why before I ran the test. It was being injected just fine, IntelliJ was incorrectly telling me the bean wasn't there.
:facepalm:
Posting this in case someone faces the same issue.

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
}

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)

Resources