Getting Started

The following example provides the minimum setup for writing a Konsist test.

Starter (preconfigured) projects containing Konsist tests are available here.

Add Repository

Add mavenCentral repository:

repositories {
    mavenCentral()
}

Add Konsist Dependency

To use Konsist, include the Konsist dependency from Maven Central:

Add the following dependency to the module\build.gradle.kts file:

dependencies {
    testImplementation("com.lemonappdev:konsist:0.14.0")
}

To achieve better test separation Konsist can be configured inside a konsistTest source set or a dedicated module. See Isolate Konsist Tests.

At a high-level Konsist check is a Unit test following multiple implicit steps.

3 steps are required for a declaration check and 4 steps are required for an architecture check:

The declaration represents Kotlin declaration eg. Kotlin class is represented by KoClassDeclaration allowing to access class name (koClassDeclaration.name), methods (koClassDeclaration.functions()), etc. See Declaration.

Create The Scope

The first step is to get a list of Kotlin files to be verified. This step is common for declaration checks and architecture checks.

The Konsist object is an entry point to the Konsist framework. The scopeFromProject method obtains the instance of the scope containing all Kotlin project files present in the project:

Konsist.scopeFromProject() // Define the scope containing all Kotlin files present in the project

To define more granular scopes see the Create The Scope page.

The following sections will present how to use a scope by writing declaration checks and architecture checks.

Declaration Check

Let's write a simple test to verify that classes (class declarations) annotated with the RestController annotation resides in controller package.

Query and Filter Declarations

To write this declaration check query all classes present in the scope:

Konsist.scopeFromProject()
    .classes() // Get scope classes

Perform additional filtering to get classes annotated with RestController annotation:

Konsist.scopeFromProject()
    .classes()
    .withAllAnnotationsOf(RestController::class) // Filter classes annotated with 'RestController'

To perform more advanced querying and filtering see the Declaration Query And Filterpage.

Assert

Assert is the final step to perform declaration verification - use assert combined with koClass.resideInPackage method to make sure that all classes (filtered in the previous step) reside in controller package:

Konsist.scopeFromProject()
    .classes()
    .withAllAnnotationsOf(RestController::class)
    .assertTrue { it.resideInPackage("..controller") } // Define the assertion

To learn more about assertions see Declaration Assert page.

The double dot syntax (..) means zero or more packages - controller package preceded by any number of packages (seePackage Wildcard syntax).

Wrap Konsist Code In Test

The above code describes declaration consistency logic. To guard this logic (and ideally, check it with every Pull Request) it will be executed in the form of a test. Konsist code can be wrapped in the test using popular testing frameworks:

class ControllerClassKonsistTest {
    @Test
    fun `classes annotated with 'RestController' annotation reside in 'controller' package`() {
        Konsist.scopeFromProject() // 1. Create a scope representing the whole project (all Kotlin files in project)
            .classes() // 2. Get scope classes
            .withAllAnnotationsOf(RestController::class) // 2. Filter classes annotated with 'RestController'
            .assertTrue { it.resideInPackage("..controller..") } // 3. Define the assertion
    }
}

The JUnit testing framework project dependency should be added to the project. See starter projects to get a complete sample project.

The above snippets present a complete example of a test verifying that all classes annotated with RestController annotation reside in the controller package. Since scope is created from all project files this test will verify existing and new classes.

Notice that test class has a KonsistTest suffix. This is the recommended approach to name Konsist tests.

Review the Snippets for more examples of declaration checks.

The above test will execute multiple assertions per test (all controllers will be verified in a single test). If you prefer better isolation each assertion can be executed as a separate test. See theDynamic Konsist Tests page.

Architectural Check

Let's write a simple test to verify that application architecture rules are preserved. In this scenario, the application follows a simple 3-layer architecture, where Presentation layer depends on Business layer and Business layer depends on Data layer. The Data layer has no layer dependencies:

Assert Architecture

Use assertArchiteture method combined with architecture definition to make sure that all classes meet architectural criteria:

Konsist
    .scopeFromProject()
    .assertArchitecture { // Assert architecture

    }

Define Layers

Create layers instances to represent project layers. Each Layer instance accepts the name (used for presenting architecture violation errors) and package used to define layers.

Konsist
    .scopeFromProject()
    .assertArchitecture {
        // Define layers
        private val presentation = Layer("Presentation", "com.myapp.presentation..")
        private val business = Layer("Business", "com.myapp.business..")
        private val data = Layer("Data", "com.myapp.data..")
    }

The presence of two dots at the end signifies that the layer is encompassed by the com.myapp.business package along with its sub-packages.

Define Architecture Assertions

The final step is to define the relations between each layer:

Konsist
    .scopeFromProject()
    .assertArchitecture {
        private val presentation = Layer("Presentation", "com.myapp.presentation..")
        private val business = Layer("Business", "com.myapp.business..")
        private val data = Layer("Data", "com.myapp.data..")

        // Define architecture assertions
        presentation.dependsOn(business)
        business.dependsOn(data)
        data.dependsOnNothing()
    }

Wrap Konsist Code In Test

The above code describes architecture consistency logic. Same as with the declaration check this logic should be executed as a unit test using you preferred testing framework:

class ArchitectureKonsistTest {
    @Test
    fun `architecture layers have dependencies correct`() {
        Konsist
            .scopeFromProject() // Define the scope containing all Kotlin files present i
            .assertArchitecture { // Assert architecture
                // Define layers
                private val presentation = Layer("Presentation", "com.myapp.presentation..")
                private val business = Layer("Business", "com.myapp.business..")
                private val data = Layer("Data", "com.myapp.data..")
    
                // Define architecture assertions
                presentation.dependsOn(business)
                business.dependsOn(data)
                data.dependsOnNothing()
            }
    }
}

The JUnit testing framework project dependency should be added to the project. See starter projects to get a complete sample project.

For more Konsist architectural checks see Protect Kotlin Project Architecture Using Konsist article.

Summary

This is the basic way of using Konsist. Take a look at this guide describing how to debug Konsist tests Debug Konsist Test to get a better understanding of how Konsist works.

Please review the Konsist documentation (this website) to read about more advanced topics and various use cases.

Last updated