SaFi Bank Space : Standard testing in "TEST" phase

When we talk about component test, in microservice world we understand this as test of the single microservice (in our case it is either, manager, gateway, or library). The component test can run locally without any connection to any environment whatsoever, nor it is connected to environment created locally using docker-compose etc. This type of the test yet can use technologies like docker for running local containers, if the tests follow gradle conventions, and there is no need to run anything outside of the test.

Usage

For running tests, the source of truth is the gradle. Gradle tooling can be used in following way:

  • ./gradlew clean test - removing all precompiled code, compile, and then run all the tests within src/test folder.

  • ./gradlew test - run all the tests within src/test folder. Be careful, that only newly created code is compiled. This can sometimes fail, but can be faster.

  • ./gradlew clean test --tests SomeTestName - removing all precompiled code, compile, and then run the test or tests within src/test folder matching the parameter. More details can be found in gradle documentation.

General notes to test types

Unit tests

Unit tests do not use any container or connection to any other code apart from tested class. These tests are fastest. Yet, they are not really able to cover more complex functionality. The unit tests are suitable for:

  • Methods without side effects.

  • Complex code, where there is too much possibilities, so from time perspective, there is better to mock the side effects, and run just unit tests.

  • Mappers. See description also below, why testing mappers might not bring expected value.

Unit tests are not suitable for:

  • Testing interfaces, as the implementation is runtime generated by a container (repository)

  • Classes with side effects. When testing service layer, mocking the repository layer brings marginal performance benefits, but we are loosing information about complexity of the interactions with a database.

  • Classes offering mostly communication between the code. It is ok to test mappers by unit tests. Yet these tests might be quite costly to write and maintain, as they must follow services expected api changes. Therefore it might be useful to not unit test them at all.

There might be classes, where some methods can be unit tested, and others not. In that case, unit testing these methods does not bring any value, as a container needs to be started for other methods anyway, and result of testing whole class from performance point of view is the same.

Container tests

Container tests are tests, where we run Micronaut container with setup similar to standard application run. The difference is the setup of the container itself in application-test.yml file. The container test typically starts containerised test Postgress DB, which is filled by DDL defined in flyway migration files. It also might start Embedded Kafka if needed. Each container test automatically runs within transaction, and is rolled back. The mentioned setup allows to test:

  • The repository layer, which is usually realised using only by interfaces.

  • Cooperation of service methods with database (especially from deadlock point of view)

  • Proper setup of the whole container (whether dependences actually work and whole IOC works as expected.

  • Cooperation of services with each other.

  • Controller layer

The downsides of the container tests are complementary to unit tests. They take longer time, especially, when DB is run within Docker. They do not bring any value in plain functional style methods.

Technical details

Structure

No matter, whether we test a class or an interface. General rule to the test is that test belongs to the same package, and has identical name to the tested class with suffix Test.

<component root>
  /src
    /main
      /kotlin
        /some
          /package
            /SuperService.kt
          /anotherpackage
            /HyperService.kt
    /test
      /kotlin
        /some
          /package
            /SuperServiceTest.kt
          /anotherpackage
            /HyperSeriviceTest.kt
      /resources
        application-test.yml - contains test configuration for the controller

Test frameworks

We write tests using:

  • JUnit 5

  • If mocking is needed, preffered framework is MockK

  • Micronaut framework test framework

  • Safi test-utils library

  • Awaitility framework for asynchronous code

  • Test containers

All of these are included within Safi test-utils, so it is sufficient to include in your gradle file:

testImplementation("ph.safibank.common:test-utils:1.+")

Test examples by layer

Repository layer test

Is typical container test. It should validate, that domain model fits to the DB structure defined by flyway migrations. Tests in this layer should typically evaluate also DB edge cases. Eg. max length (to be sure, that DB stores String long enough). Test should also evaluate negative scenarios, like missing mandatory field etc, so we can be sure, that DB structure meets our requirements.

Typical example of the test is here.

Notes:

  • Test in the example link is however missing edge cases.

Service layer test

Is also typical container test. In case of used Interface/Implementation couples, it should test the interfaces and reside in respective folder. Test should focus on business functionality of service itself, and seamless integration with repository layer. Edge cases should be considered for method signature, and also methods bodies alternative flows.

Typical example of the test is here.

Notes:

  • Test resides in wrong location (impl package).

  • Producer is mocked.

Controller layer test

Typical container test using micronaut client for access to the endpoint. Test itself should focus on behavior of REST functionality including edge cases and negative scenarios. It is typical to prepare test data by repository or service layer.

Typical example of the test is here.

Notes:

  • Uses repository to prepare test data.

  • Mocks producer.

Kafka producers and consumers test

When consumer and producer resides within the component, we can easily test their compatibility using routine in test utils. This code so far does not support test against existing AVRO schemas. Example of the usage is the test of the support class itself.

This is the test.

Notes:

Test tests the implementation of the class KafkaBaseTest.

Utils test

This is typical unit test. Test itself does not start the micronaut container, and just validates input and output of the methods in the tested class.

Example of the test is here.

Notes:

  • Unit test.

  • Missing edge cases.

The text above is based on presentation, which slides can be found in this file:

Attachments:

~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~drawio~61d41633e67ea2006bc72586~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
Untitled Diagram.drawio (application/vnd.jgraph.mxfile)
Untitled Diagram.drawio.png (image/png)
Gradle TEST Phase.pdf (application/pdf)
Gradle TEST Phase.pdf (application/pdf)
~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
~Untitled Diagram.drawio.tmp (application/vnd.jgraph.mxfile)
Untitled Diagram.drawio (application/vnd.jgraph.mxfile)
Untitled Diagram.drawio.png (image/png)