Boundary
In a MicroService architecture, the components are the services themselves. There are two typical interface in our service:
Controllers - handle http request
Kafka Listeners - handle Kafka message
By writing component tests for these two kind of interface, we can easily cover each business flow in our service which not able to guarantee in unit test.
Component tests usually take more time to write and execute since it start a Micronaut server with local dependencies(DB, Kafka and Stub servers).
Where to put the component tests
under the folder /test
How to Write Component Test
Create Test Class
Follow same steps to create test class like unit test. https://safibank.atlassian.net/wiki/spaces/SDT/pages/212697522/Unit+Test#Create-Test-Class
Write component tests for Controllers/Kafka Listeners only
Run Micronaut server with local dependencies
Component test should be annotated with
@MicronautTest
, it starts a Micronaut server using configurationapplication-test.yml
Stub external service(if it calls external service) via https://wiremock.org/
Run Kafka container(if it interacts with Kafka) via annotation
@UseKafka
(it start Kafka container only once and expose environment variableKAFKA_URL
)Run Temporal container(if it involve temporal workflow) via annotation
@UseTemporal
(it starts temporalite container and expose environment variableTEMPORAL_URL
)
Verify business flow
Test cooperation of each layers instead of business logic which already covered by unit tests
Send http request to the endpoint or Kafka message for the listener under testing
Assertion
response status
response body
changes applied to database (https://database-rider.github.io/getting-started/ )
external service stub called
Notice: The annotation @MicronautTest
has to be placed before @DBRider
@UseTemporal @UseKafka @WireMockStubFor(service = "thought-machine", exposedUrl = "TM_URL") @WireMockStubFor(service = "card-manager", exposedUrl = "SAFI_CARD_MANAGER_URL") @MicronautTest @DBRider @DataSet(skipCleaningFor = ["flyway_schema_history"]) class CardTransferTransactionControllerTest(private val embeddedServer: EmbeddedServer) { @Test @ExpectedDataSet("after-transfer-success.yml") fun `should return transaction status success when TM accept transfer transaction`() { val transactionId = "4c5c51fa-5ae5-4827-bbf5-2a6636b2ead6" schedulePostingApiResponseEvent(buildPostingEvent(transactionId, true, TRANSFER)) Given { body(buildTransferTransactionBody(transactionId)) contentType(ContentType.JSON) header(Header("idempotencyKey", "4c5c51fa-5ae5-4827-bbf5-2a6636b2ead6")) }.When { post("${embeddedServer.url}/transaction/card-transfer/sync") }.Then { statusCode(HttpStatus.CREATED.code) body("transactionId", equalTo(transactionId)) body("status", equalTo(SUCCESS.name)) wireMockServers["thought-machine"]!!.verify(1, postRequestedFor(urlEqualTo("/posting-instruction-batches:asyncCreate"))) } } }
Note:
theres a bug with the @DBRider annotation for the data setup due to micronaut use this instead:
import ph.safibank.common.testutils.dbrider.DBRider
important: dont forget to add dbunit.xml
if you are encountering a class cast exception when using http client that has something like unable to cast to scala… <TODO: INSERT STACK HERE>
SOLUTION:testImplementation("ph.safibank.common:test-utils:2.20230106-075056") { // TODO figure out how to resolve the serialization issue due to the jackson-scala module exclude("net.mguenther.kafka", "kafka-junit") }
Attachments:
image-20230117-061806.png (image/png)
Blank diagram - Page 2.png (image/png)