Comment on page
Create The Scope
Access the Kotlin files using Konsist API
Scope represents a set of Kotlin files to be further queried, filtered (Declaration Query And Filter), and verified (Declaration Assert).
Every scope contains a set of
KoFile
instances. Every KoFile
instance contains the declarations (see Declaration) representing code entities present in the file e.g.:Konsist is built on top of Kotlin Compiler Psi. It wraps the Kotlin compiler parser and provides a simple API to access Kotlin code base declarations. Konsist Declaration tree mimics the Kotlin code structure:
The scope can be created for an entire project, module, package, and Kotlin file.
The scope is dynamically built based on the Kotlin files present in the project, enabling it to adapt seamlessly as the project evolves. For instance, when the scope is set to encapsulate a specific module, any additional file introduced to that module will be automatically incorporated into the scope. This ensures that the scope consistently offers thorough and current coverage.
To execute Konsist tests, the Konsist dependency must be integrated into a module. Yet, by integrating Konsist into a single module (e.g.
app
module), Konsist can still access the entire project. The specific files evaluated are determined by the evolving scope that's been defined.Various methods can be used to obtain instances of the scope. This allows the definition of more granular Konsist tests e.g. tests covering only certain modules, source sets, packages, or folders.
The widest scope is the scope containing all Kotlin files present inside the project:
Konsist.scopeFromProject() // All Kotlin files present in the project
To print a list of files within
koScope
use the koScope.print()
method:Konsist
.scopeFromProject()
.print()
The
scopeFromModule
method allows the creation of more granular scopes based on the module name e.g. creating a scope containing all Kotlin files present in the app
module:Konsist.scopeFromModule("app")
Selection:
project/
├─ app/ <--- scope contains all files from the 'app' module
│ ├─ main/
│ │ ├─ App.kt
│ ├─ test/
│ │ ├─ AppTest.kt
├─ core/
│ ├─ main/
│ │ ├─ Core.kt
│ ├─ test/
│ │ ├─ CoreTest.kt
This approach may be helpful when refactoring existing project modules by module.
val refactoredModule1Scope = Konsist.scopeFromModule("refactoredModule1")
val refactoredModule1Scope = Konsist.scopeFromModule("refactoredModule2")
val scope = refactoredModule1Scope + refactoredModule1Scop2
scope
.classes()
...
.assertTrue { /*..*/ }
A nested module is a module that exists within another module.
The
nested modules
the feature is not complete. The community is reporting that this feature works, however, we still have to take a closer look, review expectations, and add tests. Consider this feature as experimental for now.Consider this
feature
module existing inside app
module:project/
├─ app/ <--- scope contains all files from the 'app' module
│ ├─ feature/
│ │ ├─ Feature.kt
To narrow the scope to
feature
module use:Konsist.scopeFromModule("app/feature")
The
scopeFromSourceSet
method argument allows the creation of more granular scopes based on the source set name e.g. create a scope containing all Kotlin files present in the test
source set:Konsist.scopeFromSourceSet("test")
Selection:
project/
├─ app/
│ ├─ main/
│ │ ├─ App.kt
│ ├─ test/ <--- scope contains all files the 'test' directory
│ │ ├─ AppTest.kt
├─ core/
│ ├─ main/
│ │ ├─ Core.kt
│ ├─ test/ <--- scope contains all files the 'test' directory
│ │ ├─ CoreTest.kt
To retrieve scope by using both module and source set use the
scopeFromProject
method with moduleName
and sourceSetName
arguments:Konsist.scopeFromProject(moduleName = "app", sourceSetName = "test)
project/
├─ app/
│ ├─ main/
│ │ ├─ App.kt
│ ├─ test/ <--- scope contains all files the 'test' directory
│ │ ├─ AppTest.kt
├─ core/
│ ├─ main/
│ │ ├─ Core.kt
│ ├─ test/
│ │ ├─ CoreTest.kt
The
scopeFromProduction
method allows the creation of a scope containing only a production code:Konsist.scopeFromProduction()
Selection:
project/
├─ app/
│ ├─ main/ <--- scope contains all production code files
│ │ ├─ App.kt
│ ├─ test/
│ │ ├─ AppTest.kt
├─ core/
│ ├─ main/ <--- scope contains all production code files
│ │ ├─ Core.kt
│ ├─ test/
│ │ ├─ CoreTest.kt
The
scopeFromTest
method allows the creation of a scope containing only a test code:Konsist.scopeFromTest()
Selection:
project/
├─ app/
│ ├─ main/
│ │ ├─ App.kt
│ ├─ test/ <--- scope contains all test code files
│ │ ├─ AppTest.kt
├─ core/
│ ├─ main/
│ │ ├─ Core.kt
│ ├─ test/ <--- scope contains all test code files
│ │ ├─ CoreTest.kt
The
sourceFromPackage
method allows the creation of a scope containing code present in a given package e.g. com.usecase
package:Konsist.sourceFromPackage("com.usecase..")
Selection:
project/
├─ app/
│ ├─ main/
│ │ ├─ com/
│ │ │ ├─ usecase/
│ │ │ │ ├─ UseCase.kt <--- scope contains files present from 'com.usecase' package kon
│ ├─ test/
│ │ ├─ com/
│ │ │ ├─ usecase/
│ │ │ │ ├─ UseCaseTest.kt <--- scope contains files present from 'com.usecase' package
The
scopeFromDirectory
method allows the creation of a scope containing code present in a given project folder e.g. domain
directory:val myScope = Konsist.scopeFromDirectory("app/domain")
Selection:
project/
├─ app/
│ ├─ main/
│ │ ├─ com/
│ │ │ ├─ domain/ <--- scope contains files present in 'domain' folder
It is also possible to create scope from one or more file paths:
val myScope = Konsist.scopeFromFile("app/main/domain/UseCase.kt")
We have added a new way of creating the scope from a list of files. This can help with certain development workflows e.g. runing Konsist Tests only on files modified in a given PR:
val filePaths = listOf("/domain/UseCase1.kt", "/domain/UseCase2.kt")
val myScope = Konsist.scopeFromFile(filePaths)
For even more granular control you can use the
KoScope.slice
method to retrieve a scope containing a subset of files from the given scope:// scope containing all files in the 'test' folder
koScope.slice { it.relativePath.contains("/test/") }
// scope containing all files in 'com.domain.usecase' package
koScope.slice { it.hasImport("com.domain.usecase") }
// scope containing all files in 'usecase' package and its sub-packages
koScope.slice { it.hasImport("usecase..") }
The
KoScope
can be printed to display a list of all files present in the scope. Here is an example:To reuse scope across the test class define the scope in the companion object and access it from multiple tests:
// Test.kt
class DataTest {
@Test
fun `test 1`() {
projectScope
.assertTrue { // .. }
}
fun `test 2`() {
projectScope
.assertTrue { // .. }
}
companion object {
// Create a new KoScope once for all tests
private val classesScope = Konsist
.scopeFromProject()
.classes()
}
}
To reuse scope across the multiple test classes define the scope in the file and access it from multiple test classes:
// Scope.kt is "test" source set
val projectScope = Konsist.scopeFromProject() // Create a new KoScope
// AppTest.kt
class AppKonsistTest {
@Test
fun `test 1`() {
projectScope
.objects()
.assertTrue { // .. }
}
}
// DataTest.kt
class CoreKonsistTest {
@Test
fun `test 1`() {
projectScope
.classes()
.assertTrue { // .. }
}
fun `test 2`() {
projectScope
.interfaces()
.assertTrue { // .. }
}
}
Here is the file structure representing the above snippet:
project/
├─ app/
│ ├─ test/
│ │ ├─ app
│ │ ├─ AppKonsistTest.kt
│ │ ├─ core
│ │ ├─ CoreKonsistTest.kt
│ │ ├─ Scope.kt <--- Instance of the KoScope used in both DataTest and AppTest classes.
Konsist scope supports Kotlin Operator overloading, so copes can be further combined together to create the desired scope, tailored to project needs. In this example scopes from
myFeature1
module and myFeature2
module are combined together:val featureModule1Scope = Konsist.scopeFromModule("myFeature1")
val featureModule2Scope = Konsist.scopeFromModule("myFeature2")
val refactoredModules = featureModule1Scope + featureModule2Scope
refactoredModules
.classes()
...
.assertTrue { ... }
Scope subtraction is also supported, so it is possible for example to exclude a part of a given module. Here scope is created from
myFeature
module and then the ..data..
package is excluded:val moduleScope = Konsist.scopeFromModule("myFeature")
val dataLayerScope = Konsist.scopeFromPackage("..data..")
val moduleSubsetScope = moduleScope - dataLayerScope
moduleSubsetScope
.classes()
...
.assertTrue { ... }
To print all files within the scope use the
print()
method:koScope.print()
To access specific declaration types such as interfaces, classes, constructors, functions, etc. utilize the Declaration Query And Filter.