Navigating the bustling world of modern projects can feel like a maze, with so many classes and responsibilities to juggle. Thats where Konsist comes to the rescue, tidying up our testing chaos.

Read Introduction to Konsist

But hey, why stop there? Lets take a fun twist and dive into dynamic Konsist tests.

When navigating the universe of Konsist tests, the standard approach is to execute several validations all bundled within a single test. To paint a clearer picture: imagine you have a rule (lets represent it with the tool icon 儭) ensuring that all use cases should be placed in a specific package. One static test (represented by the check icon ) can guard this rule, making sure that everything is in the right place.

Venture into any real-world project, youll encounter a labyrinth of classes and interfaces. Each of these elements plays its unique part, carrying out its own set of responsibilities. But for the sake of clarity, were going to narrow our focus. Imagine a stripped-down, no-fuss project that boasts just three use cases. Sounds manageable, right?

Our Testing Objectives

Now, with this simple project in hand, what do we want to achieve? Heres the breakdown:

  1. We want to make sure that each of our use cases isnt just floating around aimlessly; it needs to have its own test.
  2. Each use case should feel right at home in the domain.usecase package. No exceptions!

Given these criteria, if we were to stick to the tried-and-true, wed roll up our sleeves and draft two distinct JUnit5 Konsist tests:

// JUnit 5
class UseCaseKonsistTest {
@Test
fun `use case should have test`() {
Konsist
.scopeFromProject()
.classes()
.withNameEndingWith("UseCase")
.assertTrue { it.hasTestClass() }
}

@Test
fun `use case reside in domain dor usecase package`() {
Konsist
.scopeFromProject()
.classes()
.withNameEndingWith("UseCase")
.assertTrue { it.resideInPackage("..domain..usecase..") }
}
}

These tests can be written in Kotest as well:

// Kotest
class UseCaseKonsistTest : FreeSpec({
val useCases = Konsist
.scopeFromProject()
.classes()
.withNameEndingWith("UseCase")

"use case should have test" {
useCases.assertTrue(testName = this.testCase.name.testName) { it.hasTestClass() }
}

"use case should reside in ..domain.usecase.. package" {
useCases.assertTrue(testName = this.testCase.name.testName) { it.resideInPackage("..domain.usecase..") }
}
})

Each rule is represented as a separate test verifying all of the use cases:

Running these tests will produce results displayed in the IDE.

Though our existing framework with static, set-in-stone tests gets the job done, embracing dynamic tests can elevate our development journey, offering both enhanced adaptability and a smoother experience.

Think of dynamic tests as a chef who can whip up dishes on the fly based on the ingredients at hand. Instead of having a fixed menu (static tests), the chef dynamically adjusts to whats available. Similarly, dynamic tests adjust in real-time, creating test cases based on the evolving ingredients or data, like our growing list of use cases.

In this setting, imagine our project as a kitchen. The goal? To prepare a unique dish for every pairing of a rule and a use case (or in developer terms, the KoClass declaration) as directed by our head chef, Konsist. Given we have three primary ingredients (use cases) and two recipes (rules) for each, were aiming to serve up a feast of six distinct dishes (tests).

Lets transform this concept into a dynamic assessment.

JUnit offers inherent support for dynamic tests within its foundational framework, allowing developers to integrate dynamic testing features effortlessly. To operate dynamic tests in JUnit 5, the JUnit Jupiter Params dependency is essential.

class UseCaseKonsistTest {
@TestFactory
fun `use case test`(): Stream<DynamicTest> = Konsist
.scopeFromProject()
.classes()
.withNameEndingWith("UseCase")
.stream()
.flatMap { useCase ->
Stream.of(
dynamicTest("${useCase.name} should have test") {
useCase.assertTrue(testName = "${useCase.name} should have test") {
it.hasTestClass()
}
},
dynamicTest("${useCase.name} should reside in ..domain.usecase.. package") {
useCase.assertTrue(testName = "${useCase.name} should reside in ..domain.usecase.. package") {
it.resideInPackage("..domain.usecase..")
}
},
)
}
}

At the moment the API for building JUnit5 tests is a bit verbose, because test names are duplicated. We will try to improve it in the future (we are open for suggestions).

The IDE will display the tests as follows:

Kotest also provides out-of-the-box support for JUnits dynamic tests. This allows developers to easily adopt and use dynamic testing functionalities without the need for extra configurations or add-ons.

class UseCaseKonsistTest : FreeSpec({
Konsist
.scopeFromProject()
.classes()
.withNameEndingWith("UseCase")
.forEach { useCase ->
"${useCase.name} should have test" {
useCase.assertTrue(testName = this.testCase.name.testName) { it.hasTestClass() }
}
"${useCase.name} should reside in ..domain.usecase.. package" {
useCase.assertTrue(testName = this.testCase.name.testName) { it.resideInPackage("..domain..usecase..") }
}
}
})

The IDE will display the tests in a similar way to JUnit5 :

With static tests the failure is represented by the single test:

From this failure, a developer discerns the breached rule and needs to dive into the test logs to determine the cause of the violation (to pinpoint the use case breaking the given rule).

In contrast, dynamic tests immediately highlight the core issue since every use case is represented by its own distinct test. This means that the developer can quickly identify both the infringed rule and the specific class responsible for the breach.

The usage of dynamic tests enhances clarity and accelerates the test-fixing process.

Opting for dynamic tests over static ones offers developers pinpoint validations tailored to distinct use cases. While theres a slight initial learning curve, this shift notably enhances the clarity and structure of tests, simplifying the process of identifying failures. Consequently, developers spend less time navigating through lengthy error logs, resulting in a more efficient testing workflow.

Follow me on Twitter.

Source link