Kotlintest not executing test when using springmockk - spring

I tried to write an integration test for my kotlin spring application.
For this I am using the kotlintest framework. As I need to mock one of the beans in my application I also added mockk with the springmockk extension. After adding the springmockk extension the test no longer got executed.
I noticed this happens as soon as springmockk is added to the gradle testImplement dependencies, it does not even have to be imported in the application code itself.
buildscript {
ext.kotlin_version = '1.3.21'
ext.kotlintestVersion='3.4.2'
ext.spring_boot_version='2.1.4.RELEASE'
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBoot_version")
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath("org.jetbrains.kotlin:kotlin-allopen:$kotlin_version")
}
}
...
dependencies {
...
testImplementation("org.springframework.boot:spring-boot-starter-test:$springBoot_version") {
testImplementation("io.kotlintest:kotlintest-runner-junit5:$kotlintestVersion")
testImplementation("io.kotlintest:kotlintest-extensions-spring:$kotlintestVersion")
testImplementation("io.mockk:mockk:1.9.3")
// testImplementation("com.ninja-squad:springmockk:2.0.0")
}
On github I found an issue which sadly has been closed already without any proper way of using these two frameworks together: https://github.com/Ninja-Squad/springmockk/issues/26
Edit:
This is an example test, which is working when using mockkito but not when using springmockk.
#ExtendWith(SpringExtension::class)
#SpringBootTest
#AutoConfigureMockMvc
#WithMockUser(authorities = ["ROLE_TESTUSER"])
internal class MockTest : AnnotationSpec() {
override fun listeners() = listOf(SpringListener)
#Autowired
lateinit var mockMvc: MockMvc
#MockkBean
lateinit var securityHelper: SecurityHelper
#Test
fun integrationTest() {
whenever(securityHelper.someFunction()).thenReturn("test")
mockMvc.perform(MockMvcRequestBuilders.get("/some/endpoint")
).andExpect(MockMvcResultMatchers.status().isOk)
}
}
./gradlew test --rerun-tasks output:
> Configure project :
Property 'app.env' not found using profile dev: use -Papp.env=dev to define the environment for 'SPRING_PROFILES_ACTIVE'
> Task :compileKotlin
BUILD SUCCESSFUL in 56s
5 actionable tasks: 5 executed

Mock Bean with springMockk
To use #MockkBean you need to add springmockk and remove mockito core from the spring-boot-starter-test in your gradle file like:
testImplementation("io.mockk:mockk:1.9.3")
testImplementation("com.ninja-squad:springmockk:2.0.2")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(module = "mockito-core")
}
Then your bean should be mocked with:
#MockkBean
lateinit var securityHelper: SecurityHelper
Mock Bean with MockK only
You can mock the bean just using mockK by modifying the #TestConfiguration and setting the profile of the mock Bean to be the same as the one used for your test:
#TestConfiguration
class ControllerTestConfig {
#Bean
#Profile("test")
fun securityHelper(): SecurityHelper {
val securityHelperMock: SecurityHelper = mockk(relaxed = true)
every { securityHelperMock.someFunction() } returns "test"
return securityHelperMock
}
}
You can force use the TestConfig by putting it in your #SpringBootTest:
#SpringBootTest(
classes = [YourApplication::class, ControllerTestConfig::class]
)

Related

Overriding Springboot Beans in integration test

I know there are a lot of examples out there about overriding beans in integration tests, but I'm getting a bit confused with Springboot versions, and I don't know what am I doing wrong.
I'm basically running a #SpringBootTest with #ActiveProfiles("test") and RestAssured. All annotations are in a base class I called BaseIntegrationTest, which looks like the following:
#ActiveProfiles("test")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration(
initializers = [BaseIntegrationTest.Companion.Initializer::class],
classes = [BaseIntegrationTest.Companion.ClockConfiguration::class]
)
abstract class BaseIntegrationTest {
#LocalServerPort
var localPort: Int = 0
#BeforeEach
fun beforeEachTest() {
RestAssured.port = localPort
}
companion object {
#TestConfiguration
class ClockConfiguration {
#Bean
fun mockClock(): Clock {
return Clock.fixed(
LocalDate
.of(2022, 1, 1)
.atTime(0, 0)
.toInstant(ZoneOffset.UTC),
ZoneId.of("UTC")
)
}
#Bean
fun mockCountryService(): CountryService {
return MockCountryService()
}
}
class MockCountryService : CountryService() {
override fun findByCode(countryCode: String): Country? {
return null
}
}
}
}
As you can see, I have a nested class with #TestConfiguration annotation (as I read it automatically recogizes it). By the way, I also included this CountryService bean because I was initially trying to mock the Clock, without success. I used this service to make sure that it was not a clock issue, but a bean overriding issue.
But when I run the tests, and put breakpoints along the production code, I see that what gets instantiated are the real beans, not these ones. I also tried to use #Configuration instead of #TestConfiguration, and adding #Primary to the beans here, but no luck.
If I remove the original beans, and I leave only these ones, then when debugging the production code I actually get the mocked beans, so I guess it has something to do in overriding them.
Springboot version is 2.7.2
Can anyone help me with that?
Thanks!

Spring #DataJpaTest with JUnit 5

There doesn't seem to be a specific standard way I can find online that makes #DataJpaTest to run correctly.
Is it true that #DataJpaTest is not being used nowadays and all tests are run at the service or controller level using #SpringBootTest?
#Repository
public interface MyBeanRepository extends JpaRepository<MyBean, Long> {
}
#Configuration
#EnableJpaRepositories("com.app.repository.*")
#ComponentScan(basePackages = { "com.app.repository.*" })
public class ConfigurationRepository {
}
#Entity
public class MyBean {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Version
#Column(name = "version")
private Integer version;
#NotNull
#Size(min = 2)
private String name;
}
#DataJpaTest
public class MyBeanIntegrationTest {
#Autowired
MyBeanRepository myBeanRepository;
#Test
public void testMarkerMethod() {
}
#Test
public void testCount() {
Assertions.assertNotNull(myBeanRepository , "Data on demand for 'MyBean' failed to initialize correctly");
}
}
Running using the Eclipse->Run Junit shows these logs.
java.lang.IllegalStateException: Unable to find a #SpringBootConfiguration, you need to use #ContextConfiguration or #SpringBootTest(classes=...) with your test
at org.springframework.util.Assert.state(Assert.java:73)
Running using gradle test shows the error that init failed.
FAILURE: Build failed with an exception.
* What went wrong:
Test failed.
Failed tests:
Test com.app.repository.MyBeanIntegrationTest#initializationError (Task: :test)
Here is the gradle script.
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin")
}
}
plugins {
id 'org.springframework.boot' version '2.1.5.RELEASE'
id 'java'
id 'eclipse'
}
apply plugin: 'io.spring.dependency-management'
group = 'com.app'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenLocal()
jcenter()
mavenCentral()
}
test {
useJUnitPlatform()
}
dependencies {
// This dependency is exported to consumers, that is to say found on their compile classpath
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
runtimeOnly 'org.hsqldb:hsqldb'
testImplementation ('org.springframework.boot:spring-boot-starter-test') {
// exlcuding junit 4
exclude group: 'junit', module: 'junit'
}
/**
Test Dependencies Follows
**/
// junit 5 api
testCompile "org.junit.jupiter:junit-jupiter-api:5.2.0"
// For junit5 parameterised test support
testCompile "org.junit.jupiter:junit-jupiter-params:5.2.0"
// junit 5 implementation
testRuntime "org.junit.jupiter:junit-jupiter-engine:5.2.0"
// Only required to run junit5 test from IDE
testRuntime "org.junit.platform:junit-platform-launcher"
}
EDIT:
This has been solved and committed at the same repository.
https://github.com/john77eipe/spring-demo-1-test
It was a good idea to add a Github link. I can see following issues there:
1) If you can't have a main class annotated with #SpringBootApplication, you can use:
#SpringBootConfiguration
#EnableAutoConfiguration
#ComponentScan(basePackages = "com.app.repository")
public class MySampleApplication {
}
2) Change annotations over your ConfigurationRepository class to:
#EnableJpaRepositories("com.app.repository")
#ComponentScan(basePackages = { "com.app.repository" })
public class ConfigurationRepository {
That should let us proceed to the next point:
3) Your MyBeanIntegrationTest should be annotated as:
#SpringBootTest(classes = MyAppApplication.class)
public class MyBeanIntegrationTest {
4) In application.yml you have a small issue with indentation in the last line. Convert tab so spaces and it should be fine.
5) Next thing is MyBeanRepository interface. You can't use a method named findOne there. Thing is, that in interfaces marked as JpaRepository or CrudRepository and so on, methods names need to follow certain rules. If you mark that it will be a repository containing type MyBean your method name should be changed to findById, because Spring will look for a property named id in your bean. Naming it by findOne will cause test to fail with:
No property findOne found for type MyBean!
After fixing these things, your tests pass on my env.
I hope this helps!

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)

Kotlin spring-boot #ConfigurationProperties

I'm trying to create the following bean AmazonDynamoDBAsyncClientProvider. I've application.properties that defines endpoint and tablePrefix which I'm trying to inject using #ConfigurationProperties
Following is the code snippet for the same. When I run my spring-boot app it doesn't work.
I've tried doing the same ConfigurationProperties class using a regular java class which does set those properties but when it comes to AmazonDynamoDBAsyncClientProvider, the properties are empty. What am I missing here?
#Component
open class AmazonDynamoDBAsyncClientProvider #Autowired constructor(val dynamoDBConfiguration: DynamoDBConfig){
#Bean open fun getAmazonDBAsync() = AmazonDynamoDBAsyncClientBuilder.standard()
.withEndpointConfiguration(
AwsClientBuilder.EndpointConfiguration(dynamoDBConfiguration.endpoint, dynamoDBConfiguration.prefix))
.build()
}
here is the kotlin bean that I'm trying to autowire with configuration
#Component
#ConfigurationProperties(value = "dynamo")
open class DynamoDBConfig(var endpoint: String="", var prefix: String="")
finally heres the regular java bean that does get populated with ConfigurationProperties but when it gets Autowired I see those properties being empty/null
#Component
#ConfigurationProperties("dynamo")
public class DynamoDBConfiguration {
private String endpoint;
private String tablePrefix;
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public String getTablePrefix() {
return tablePrefix;
}
public void setTablePrefix(String tablePrefix) {
this.tablePrefix = tablePrefix;
}
}
Have you tried getting rid of the #Component annotation on your ConfigurationProperties class?
Here is what I have done with Kotlin and Spring, hope it helps.
I am trying to leverage the kotlin-spring and kotlin-allopen gradle plugin
dependencies {
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlinVersion"
classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlinVersion"
}
apply plugin: 'kotlin-spring'
apply plugin: 'kotlin-noarg'
noArg {
annotation("your.noarg.annotation.package.NoArg")
}
They do make spring development with kotlin a lot easier.
#ConfigurationProperties("dynamo")
#NoArg
data class DynamoDBConfiguration(var endpoint: String, var prefix: String)
I tried your configuration class and it gets populated. I think your mistake is in the way you are trying to create the bean, the function needs to be in a class annotated with #Configuration, this should work:
#Configuration
class Beans {
#Bean
fun getAmazonDBAsync(config: DynamoDBConfiguration) =
AmazonDynamoDBAsyncClientBuilder.standard()
.withEndpointConfiguration(
AwsClientBuilder.EndpointConfiguration(config.endpoint, config.prefix)
)
.build()
}
Spring will inject the config for you, as long as you annotate the config with #Component, like you did above.
I had a similar problem and fixed it this way:
I defined the configuration properties class with lateinit vars:
#ConfigurationProperties(prefix = "app")
open class ApplicationConfigProperties {
lateinit var publicUrl: String
}
Then configured a bean in my spring boot application:
#SpringBootApplication
open class Application {
#Bean open fun appConfigProperties() = ApplicationConfigProperties()
}

Spring boot testcase is failing due to (ReactiveWebApplicationContext must be an instance of interface ConfigurableWebApplicationContext)

I have created new spring boot project with version springBootVersion = '2.0.0.BUILD-SNAPSHOT'. When I start my application it's running without any errors, where as test case is failing with error
[org.springframework.boot.context.embedded.ReactiveWebApplicationContext]
must be an instance of interface
org.springframework.web.context.ConfigurableWebApplicationContext
This is my gradle dependency for test.
testCompile('org.springframework.boot:spring-boot-starter-test')
here is test case
#EnableAutoConfiguration
#RunWith(SpringRunner.class)
#SpringBootTest(classes=MarketDataApplication.class)
public class MarketDataApplicationTests {
#Test
public void contextLoads() {
}
}
Am I missing any dependency?

Resources