Blog Infos
Author
Published
Topics
Published
Topics

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

Read Introduction to Konsist

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

Essence of Static 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 (let’s 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, you’ll 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, we’re 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? Here’s the breakdown:

  1. We want to make sure that each of our use cases isn’t 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, we’d roll up our sleeves and draft two distinct JUnit5 Konsist tests:

OUR VIDEO RECOMMENDATION

Jobs

No results found.

// 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.

Dynamic Tests

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 what’s 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, we’re aiming to serve up a feast of six distinct dishes (tests).

Let’s 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 JUnit’s 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 :

Advantages of using Dynamic Tests

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.

Summary

Opting for dynamic tests over static ones offers developers pinpoint validations tailored to distinct use cases. While there’s 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.

Links

This article was previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
blog
Automation is a key point of Software Testing once it make possible to reproduce…
READ MORE
blog
Drag and Drop reordering in Recyclerview can be achieved with ItemTouchHelper (checkout implementation reference).…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu