Loading test application.yml properties - spring

I have a test properties file under src/test/resources/application.yml. But I cannot get the values to load in my unit test. I have the following class:
#ConfigurationProperties("snmp")
open class SnmpProperties {
var port: Int = 1611
lateinit var protocol: String
lateinit var host: String
override fun toString(): String {
return "SnmpProperties(port=$port, protocol='$protocol', host='$host')"
}
}
which in the production code, loads in the values from /src/main/resources/application.yml.
snmp:
port: 1161
protocol: udp
host: 0.0.0.0
Unit test class:
#CamelSpringBootTest
#SpringBootApplication
#EnableAutoConfiguration
open class SnmpRouteTest : CamelTestSupport() {
#Autowired
lateinit var snmpProperties: SnmpProperties
#Mock
lateinit var repository: IPduEventRepository
#InjectMocks
lateinit var snmpTrapRoute: SnmpTrapRoute
#Before
fun setup() {
initMocks(this)
}
I have tried to add a test profile to each application.yml files to see if that adding #ActiveProfiles("test") worked, but it didn't.
src/main/resources/application.yml &
src/test/resources/application.yml
# Test profile
spring:
profiles: test
snmp:
port: 1161
protocol: udp
host: 0.0.0.0
I've also created a TestConfiguration class which creates the SnmpProperties bean and autowire it into the test class using #EnableConfigurationProperties(TestConfiguration::class):
#Configuration
#EnableConfigurationProperties(SnmpProperties::class)
open class TestConfiguration {
#Bean
open fun snmpProperties() = SnmpProperties()
}
Again, no go. The error I get is:
Cannot instantiate #InjectMocks field named 'snmpTrapRoute' of type 'class org.meanwhile.in.hell.camel.snmp.receiver.route.SnmpRoute'.
You haven't provided the instance at field declaration so I tried to construct the instance.
However the constructor or the initialization block threw an exception : Parameter specified as non-null is null: method org.meanwhile.in.hell.camel.snmp.receiver.route.SnmpTrapRoute.<init>, parameter snmpProperties

Make sure to check your project structure. The properties file should be on the classpath in order for Spring Boot to find and use it.
For example the project structure as defined by Maven here: https://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html
In case of Maven your configuration files should be put in these directories:
src/main/resources/application.yml
src/test/resources/application.yml

It looks like the bean is not created (hence the null error).
Try to either:
add #Configuration on top of your SnmpProperties configuration class
add #EnableConfigurationProperties(SnmpProperties.class) on top of your test class
Source: https://www.baeldung.com/configuration-properties-in-spring-boot

#CamelSpringBootTest
#SpringBootTest(classes = [SnmpTrapReceiverCamelApplication::class])
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
#DisableJmx(false)
#ExtendWith(MockitoExtension::class)
#EnableAutoConfiguration
class SnmpTrapRouteTest {
object TestSnmpConstants {
const val SNMP_REAL_ENDPOINT_ID = "snmp-trap-route"
const val SNMP_DIRECT_REPLACEMENT_ENDPOINT = "direct:snmp-from"
const val TRAP_REQUEST_ID = 123456789
const val TRAP_OID = "1.2.3.4.5"
const val TRAP_PAYLOAD = "snmp-trap-payload"
}
#MockBean
lateinit var repository: IPduEventRepository
#Produce
lateinit var producerTemplate: ProducerTemplate
#Autowired
lateinit var camelContext: CamelContext
#Test
#Throws(Exception::class)
fun `Should call save method on the repository when PDU TRAP event supplied`() {
// Replace our SNMP consumer route with a dummy route than can be called from a producer internally.
// Since our snmp endpoint is an asynchronous consumer (meaning it only receives data from external events)
// we need to use the "direct:" component to allow a producer to internally call what is ordinarily an external
// event-driven endpoint. Otherwise we will get a Connection Refused error, as we cannot access the external
// system/socket.
AdviceWithRouteBuilder.adviceWith(camelContext, TestSnmpConstants.SNMP_REAL_ENDPOINT_ID) { routeBuilder ->
routeBuilder.replaceFromWith(TestSnmpConstants.SNMP_DIRECT_REPLACEMENT_ENDPOINT)
}
// Create the PDU object to send to the SNMP endpoint
val trap = PDU()
trap.type = PDU.TRAP
trap.requestID = Integer32(TestSnmpConstants.TRAP_REQUEST_ID)
trap.add(VariableBinding(OID(TestSnmpConstants.TRAP_OID), OctetString(TestSnmpConstants.TRAP_PAYLOAD)))
// "direct:" endpoints only send DefaultMessage objects. These are not castable to SnmpMessage objects,
// so need to overwrite the exchange IN message to be an SnmpMessage object
val exchange = DefaultExchange(camelContext)
exchange.setIn(SnmpMessage(camelContext, trap))
// ProducerTemplates need a default endpoint specified.
// The ProducerTemplate provides us with a producer that can directly deliver messages to consumers defined
// in the camelContext, using the "direct:" component (see above)
producerTemplate.setDefaultEndpointUri(TestSnmpConstants.SNMP_DIRECT_REPLACEMENT_ENDPOINT)
producerTemplate.send(exchange)
// Verify that the repository.save() was invoked
verify(repository, atLeast(1)).save(any())
}
}

Related

How do I properly Unit Test Spring Boot Reactive app with Kotlin & Kotest

I'm fairly new to Kotlin and Kotest, been scratching my head on how to properly unit test webflux application. Looking over kotest samples here, to me that looks like it's spinning up a WebTestClient server and is more like integration test(please correct me if I'm wrong).
My app is fairly simple, I have a rest controller that I'm using constructor injecting to inject my service.
This service uses WebClient to call a different external service that returns a Mono<MyResponse>. And my test looks something like so:
#SpringBootTest
class MyControllerTest : FunSpec({
lateinit var service: MyService
lateinit var controller: MyController
beforeTest {
service = mockk()
controller = MyController(service)
}
test("should return my response") {
val myResponse = MyResponse(name = "John Doe")
every { service.getName(any()) } returns Mono.just(myResponse)
val response = controller.getName()
verify { service.getName(any()) }
response shouldBe myResponse
}
})
The error I'm getting is:
expected: MyResponse(name = "John Doe")
actual: Monojust

How to set up Spring Kafka test using EmbeddedKafkaRule/ EmbeddedKafka to fix TopicExistsException Intermittent Error?

I have been having issues with testing my Kafka consumer and producer. The integration tests fail intermittently with TopicExistsException.
This is how my current test class - UserEventListenerTest looks like for one of the consumers:
#SpringBootTest(properties = ["application.kafka.user-event-topic=user-event-topic-UserEventListenerTest",
"application.kafka.bootstrap=localhost:2345"])
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class UserEventListenerTest {
private val logger: Logger = LoggerFactory.getLogger(javaClass)
#Value("\${application.kafka.user-event-topic}")
private lateinit var userEventTopic: String
#Autowired
private lateinit var kafkaConfigProperties: KafkaConfigProperties
private lateinit var embeddedKafka: EmbeddedKafkaRule
private lateinit var sender: KafkaSender<String, UserEvent>
private lateinit var receiver: KafkaReceiver<String, UserEvent>
#BeforeAll
fun setup() {
embeddedKafka = EmbeddedKafkaRule(1, false, userEventTopic)
embeddedKafka.kafkaPorts(kafkaConfigProperties.bootstrap.substringAfterLast(":").toInt())
embeddedKafka.before()
val producerProps: HashMap<String, Any> = hashMapOf(
ProducerConfig.BOOTSTRAP_SERVERS_CONFIG to kafkaConfigProperties.bootstrap,
ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG to "org.apache.kafka.common.serialization.StringSerializer",
ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG to "com.project.userservice.config.MockAvroSerializer"
)
val senderOptions = SenderOptions.create<String, UserEvent>(producerProps)
sender = KafkaSender.create(senderOptions)
val consumerProps: HashMap<String, Any> = hashMapOf(
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG to kafkaConfigProperties.bootstrap,
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG to "org.apache.kafka.common.serialization.StringDeserializer",
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG to kafkaConfigProperties.deserializer,
ConsumerConfig.AUTO_OFFSET_RESET_CONFIG to "earliest",
"schema.registry.url" to kafkaConfigProperties.schemaRegistry,
ConsumerConfig.GROUP_ID_CONFIG to "test-consumer"
)
val receiverOptions = ReceiverOptions.create<String, UserEvent>(consumerProps)
.subscription(Collections.singleton("some-topic-after-UserEvent"))
receiver = KafkaReceiver.create(receiverOptions)
}
}
// Some tests
// Not shown as they are irrelevant
...
...
...
The UserEventListener class consumes a message from user-event-topic-UserEventListenerTest and publishes a message to some-topic-after-UserEvent.
As you can see from the setup, I have a test producer that will publish a message to user-event-topic-UserEventListenerTest so that I can test whether UserEventListener consumes the message and a test consumer that will consume the message from the some-topic-after-UserEvent so that I can see if UserEventListener publishes a message to some-topic-after-UserEvent after processing the record.
The KafkaConfigProperties class is as follows.
#Component
#ConfigurationProperties(prefix = "application.kafka")
data class KafkaConfigProperties(
var bootstrap: String = "",
var schemaRegistry: String = "",
var deserializer: String = "",
var userEventTopic: String = "",
)
And the application.yml looks like this.
application:
kafka:
user-event-topic: "platform.user-events.v1"
bootstrap: "localhost:9092"
schema-registry: "http://localhost:8081"
deserializer: com.project.userservice.config.MockAvroDeserializer
Error logs
com.project.userservice.user.UserEventListenerTest > initializationError FAILED
kafka.common.KafkaException:
at org.springframework.kafka.test.EmbeddedKafkaBroker.createTopics(EmbeddedKafkaBroker.java:354)
at org.springframework.kafka.test.EmbeddedKafkaBroker.lambda$createKafkaTopics$4(EmbeddedKafkaBroker.java:341)
at org.springframework.kafka.test.EmbeddedKafkaBroker.doWithAdmin(EmbeddedKafkaBroker.java:368)
at org.springframework.kafka.test.EmbeddedKafkaBroker.createKafkaTopics(EmbeddedKafkaBroker.java:340)
at org.springframework.kafka.test.EmbeddedKafkaBroker.afterPropertiesSet(EmbeddedKafkaBroker.java:284)
at org.springframework.kafka.test.rule.EmbeddedKafkaRule.before(EmbeddedKafkaRule.java:114)
at com.project.userservice.user.UserEventListenerTest.setup(UserEventListenerTest.kt:62)
Caused by:
java.util.concurrent.ExecutionException: org.apache.kafka.common.errors.TopicExistsException: Topic 'user-event-topic-UserEventListenerTest' already exists.
at org.apache.kafka.common.internals.KafkaFutureImpl.wrapAndThrow(KafkaFutureImpl.java:45)
at org.apache.kafka.common.internals.KafkaFutureImpl.access$000(KafkaFutureImpl.java:32)
at org.apache.kafka.common.internals.KafkaFutureImpl$SingleWaiter.await(KafkaFutureImpl.java:104)
at org.apache.kafka.common.internals.KafkaFutureImpl.get(KafkaFutureImpl.java:272)
at org.springframework.kafka.test.EmbeddedKafkaBroker.createTopics(EmbeddedKafkaBroker.java:351)
... 6 more
Caused by:
org.apache.kafka.common.errors.TopicExistsException: Topic 'user-event-topic-UserEventListenerTest' already exists.
What I have tried:
Use different bootstrap server address in each test by specifying the bootstrap configuration, e.g. #SpringBootTest(properties = ["application.kafka.bootstrap=localhost:2345"])
Use different topic names in each test by overwriting the topic configuration via #SpringBootTest just like the bootstrap server overwrite in the previous bullet point
Add #DirtiesContext to each test class
Package versions
Kotlin 1.3.61
Spring Boot - 2.2.3.RELEASE
io.projectreactor.kafka:reactor-kafka:1.2.2.RELEASE
org.springframework.kafka:spring-kafka-test:2.3.4.RELEASE (test implementation only)
Problem
I have multiple test classes that use EmbeddedKafkaRule and are set up more or less the same away. For each of them, I specify different kafka bootstrap server address and topic names, but I still see the TopicAlreadyExists exceptions intermittently.
What can I do to make my test classes consistent?
I specify different kafka bootstrap server address and topic names, but I still see the TopicAlreadyExists exceptions intermittently
That makes no sense; if they have a new port each time, and especially new topic names, it's impossible for the topic(s) to already exist.
Some suggestions:
Since you are using JUnit5, don't use the JUnit4 EmbeddedKafkaRule, use EmbeddedKafkaBroker instead; or simply add #EmbeddedKafka and the broker will be added as a bean to the Spring application context and its life cycle managed by Spring (use #DirtiesContext to destroy); for non-Spring tests, the broker will be created (and destroyed) by the JUnit5 EmbeddedKafkaCondition and is available via EmbeddedKafkaCondition.getBroker().
Don't use explicit ports; let the broker use its default random port and use embeddedKafka.getBrokersAsString() for the bootstrap servers property.
If you must manage the brokers yourself (in #BeforeAll), destroy() them in #AfterAll.

#Transactional in Spring Boot - I believe prerequisites are covered (public, external invocation), but testing indicates no transaction

I'm trying to get a Kotlin function to operate transactionally in Spring Boot, and I've looked at several sources for information, such as https://codete.com/blog/5-common-spring-transactional-pitfalls/ and Spring #Transaction method call by the method within the same class, does not work?. I believe I have the prerequisites necessary for the #Transactional annotation to work - the function is public and being invoked externally, if my understanding is correct. My code currently looks like this:
interface CreateExerciseInstance {
operator fun invoke(input: CreateExerciseInstanceInput): OpOutcome<CreateExerciseInstanceOutput>
}
#Component
class CreateExerciseInstanceImpl constructor(
private val exerciseInstanceRepository: ExerciseInstanceRepository, // #Repository
private val activityInstanceRepository: ActivityInstanceRepository, // #Repository
private val exerciseInstanceStepRepository: ExerciseInstanceStepRepository // #Repository
) : CreateExerciseInstance {
#Suppress("TooGenericExceptionCaught")
#Transactional
override fun invoke(input: CreateExerciseInstanceInput): OpOutcome<CreateExerciseInstanceOutput> {
...
val exerciseInstanceRecord = ... // no in-place modification of repository data
val activityInstanceRecords = ...
val exerciseInstanceStepRecords = ...
return try {
exerciseInstanceRepository.save(exerciseInstanceRecord)
activityInstanceRepository.saveAll(activityInstanceRecords)
exerciseInstanceStepRepository.saveAll(exerciseInstanceStepRecords)
Outcome.Success(...)
} catch (e: Exception) {
Outcome.Failure(...)
}
}
}
My test currently looks like this:
#ExtendWith(SpringExtension::class)
#SpringBootTest
#Transactional
class CreateExerciseInstanceTest {
#Autowired
private lateinit var exerciseInstanceRepository: ExerciseInstanceRepository
#Autowired
private lateinit var exerciseInstanceStepRepository: ExerciseInstanceStepRepository
#Autowired
private lateinit var activityInstanceRepository: ActivityInstanceRepository
#Test
fun `does not commit to exercise instance or activity repositories when exercise instance step repository throws exception`() {
... // data setup
val exerciseInstanceStepRepository = mockk<ExerciseInstanceStepRepository>()
val exception = Exception("Something went wrong")
every { exerciseInstanceStepRepository.save(any<ExerciseInstanceStepRecord>()) } throws exception
val createExerciseInstance = CreateExerciseInstanceImpl(
exerciseInstanceRepository = exerciseInstanceRepository,
activityInstanceRepository = activityInstanceRepository,
exerciseInstanceStepRepository = exerciseInstanceStepRepository
)
val outcome = createExerciseInstance(...)
assert(outcome is Outcome.Failure)
val exerciseInstances = exerciseInstanceRepository.findAll()
val activityInstances = activityInstanceRepository.findAll()
assertThat(exerciseInstances.count()).isEqualTo(0)
assertThat(activityInstances.count()).isEqualTo(0)
}
}
The test fails with:
org.opentest4j.AssertionFailedError:
Expecting:
<1>
to be equal to:
<0>
but was not.
at assertThat(exerciseInstances.count()).isEqualTo(0). Is the function actually non-public or being invoked internally? Have I missed some other prerequisite?
This test doesn't say anything about your component not being transactional.
First, you create an instance yourself rather than using the one created by Spring. So Spring knows nothing about this instance, and can't possibly warp it into a transactional proxy.
Second, the component doesn't throw any runtime exception, So Spring doesn't rollback the transaction.

kotlin : unit test with mock injection (mockK)

I followed step by step instructions from many blogs for implementing a mock with MockK:
class SWServiceImplTest {
#MockK
lateinit var externalApi: ExternalApiService
#InjectMockKs
lateinit var SWService: SWServiceImpl
#Before
fun setUp() = MockKAnnotations.init(this)
#Test
fun SWCharacterReturnsCorrectValues() {
every { externalApi.get<Characters>(Utils.SW_API) } returns mockCharacters()
val result = SWService.swCharacter!!
assertEquals("blue", result.first().color?.toLowerCase())
assertEquals(result.size, 3)
}
}
I want to inject externalApi into my SWService service and mock the get method of the injected object (externalApi) but it seems that the mock is ignored.
logs :
15:09:54.497 [main] DEBUG io.mockk.impl.instantiation.AbstractMockFactory - Creating mockk for <error "java.lang.NoClassDefFoundError: kotlin/coroutines/intrinsics/IntrinsicsKt"> name=externalApi#1
15:09:56.820 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET https://xxx.xxx/
15:09:57.038 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[application/json, application/*+json]
org.springframework.web.client.RestClientException: No HttpMessageConverter for java.lang.Object and content type ""
In my SWService file, externalApi is initialized in a companion object :
companion object{
val api = ExternalApiService()
}
Something wrong in my implementation ?
Thanks
Kotlin generates a inner class for companion object {} called Companion. This Companion class would have only getters for the fields declared (in your case getApi()). But the field is maintained by outer class SWService.
So equivalent java class for SWService would look like.
public final class SWService {
private static final ExternalApiService api = new ExternalApiService();
public static final class Companion() {
public final ExternalApiService getApi() {
return SWService.api;
}
}
}
Now you want to mock api which is static field. This can be done using powermockito.
Add this dependency,
testImplementation "org.powermock:powermock-api-mockito2:2.0.0-beta.5"
And in your test,
public final class SWServiceTest {
#MockK
lateinit var api: ExternalApiService
var service = SWService()
#Before
fun setUp() {
MockKAnnotations.init(this)
Whitebox.setInternalState(SWService::class.java,"api", api);
}
}
Hope it helps.

How can I make WireMock port more dynamic to use it for testing service

I am using wiremock to mock github api to do some testing of my service.
The service calls github api. For the tests I am setting endpoint property to
github.api.endpoint=http://localhost:8087
This host and port are the same as wiremock server #AutoConfigureWireMock(port = 8087) so I can test different scenarios like : malformed response, timeouts etc.
How can I make this port dynamic to avoid case when it is already used by system ? Is there a way to get wiremock port in tests and reassign endpoint property ?
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureWireMock(port = 8087)
#TestPropertySource(properties ={"github.api.endpoint=http://localhost:8087"})
public class GithubRepositoryServiceTestWithWireMockServer {
#Value("${github.api.client.timeout.milis}")
private int githubClientTimeout;
#Autowired
private GithubRepositoryService service;
#Test
public void getRepositoryDetails() {
GithubRepositoryDetails expected = new GithubRepositoryDetails("niemar/xf-test", null,
"https://github.com/niemar/xf-test.git", 1, "2016-06-12T18:46:24Z");
stubFor(get(urlEqualTo("/repos/niemar/xf-test"))
.willReturn(aResponse().withHeader("Content-Type", "application/json").withBodyFile("/okResponse.json")));
GithubRepositoryDetails repositoryDetails = service.getRepositoryDetails("niemar", "xf-test");
Assert.assertEquals(expected, repositoryDetails);
}
#Test
public void testTimeout() {
GithubRepositoryDetails expected = new GithubRepositoryDetails("niemar/xf-test", null,
"https://github.com/niemar/xf-test.git", 1, "2016-06-12T18:46:24Z");
stubFor(get(urlEqualTo("/repos/niemar/xf-test"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBodyFile("/okResponse.json")
.withFixedDelay(githubClientTimeout * 3)));
boolean wasExceptionThrown = false;
try {
GithubRepositoryDetails repositoryDetails = service.getRepositoryDetails("niemar", "xf-test");
} catch (GithubRepositoryNotFound e) {
wasExceptionThrown = true;
}
Assert.assertTrue(wasExceptionThrown);
}
You have to set the WireMock port to 0 so that it chooses a random port and then use a reference to this port (wiremock.server.port) as part of the endpoint property.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureWireMock(port = 0)
#TestPropertySource(properties = {
"github.api.endpoint=http://localhost:${wiremock.server.port}"
})
public class GithubRepositoryServiceTestWithWireMockServer {
....
}
See also Spring Cloud Contract WireMock.
I know this is a bit old post but still there is a documented way to have these ports dynamically. Read more here: Getting started. Just scroll down a bit to 'Random port numbers'.
From the documentation there:
What you need to do is to define a Rule like so
#Rule
public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort().dynamicHttpsPort());
And then access them via
int port = wireMockRule.port();
int httpsPort = wireMockRule.httpsPort();
One more way, you can use dynamic port without conflict is
import org.springframework.util.SocketUtils;
int WIREMOCK_PORT = SocketUtils.findAvailableTcpPort();
public WireMockRule wireMockServer = new WireMockRule(WIREMOCK_PORT);
if you want to access it from properties file, then we have wiremock.server.portprovided by Wiremock
"github.api.endpoint=http://localhost:${wiremock.server.port}"
I am not aware of #AutoConfigureWireMock but if you are manually starting wiremock and setting up mocks, while starting spring you can setup a random port number utilizing spring random. A sample will look like this
in your wiremock class
#Component
public class wiremock {
#Value("${randomportnumber}")
private int wiremockPort;
public void startWiremockServer() {
WireMock.configureFor("localhost", wiremockPort);
wireMockServer = new com.github.tomakehurst.wiremock.WireMockServer(wireMockConfig().port(wiremockPort).extensions
(MockedResponseHandler.class));
wireMockServer.start();
}
}
In your test class
//however you want to configure spring
public class wiremock {
#Value("${github.api.endpoint}")
private String wiremockHostUrl;
//use the above url to get stubbed responses.
}
in your application.properties file
randomportnumber=${random.int[1,9999]}
github.api.endpoint=http://localhost:${randomportnumber}

Resources