0
\$\begingroup\$

I build web application using Kotlin and Spring Boot, I use MockMvc to write integration test.

I'm not satisfied how assertion looks like, here is typical assertion:

        @Test
        fun `Get profile by id`() {
            thereIsATeacher(lissaSkipper)

            mockMvc.get("/teacher/{id}", "52058608")
                .andExpect {
                    status { isOk() }
                    jsonPath("$.id") { value("52058608") }
                    jsonPath("$.slug") { value("lissa-skipper-math-teacher") }
                }
        }

the jsonPath("$.id") { value("52058608") }, for me, is not good enough, but it's very popular. I spent a few hours and tried to build "domain assertion", using DSL. Now the test looks:

        @Test
        fun `Get profile by id`() {
            thereIsATeacher(lissaSkipper)

            mockMvc.get("/teacher/{id}", "52058608")
                .andExpect {
                    status { isOk() }

                    teacherProfile {
                        id("52058608")
                        slug("lissa-skipper-math-teacher")
                        profilePhoto {
                            url("https://example.com/photo.jpg")
                            alt("Lissa Skipper - math teacher")
                        }
                        joined("2023-01-22T10:00:00Z")
                    }
                }
        }

and here are assertion classes

class TeacherProfileRestAssertion internal constructor(private val dsl: MockMvcResultMatchersDsl) {

    fun id(expectedValue: String) {
        dsl.jsonPath("$.id") { value(expectedValue) }
    }

    fun slug(expectedValue: String) {
        dsl.jsonPath("$.slug") { value(expectedValue) }
    }

    fun profilePhoto(imageAssertion: ImageRestAssertion.() -> Unit) {
        ImageRestAssertion(dsl, "$.profilePhoto").apply(imageAssertion)
    }
// ...
}

fun MockMvcResultMatchersDsl.teacherProfile(assertion: TeacherProfileRestAssertion.() -> Unit) {
    TeacherProfileRestAssertion(this).apply(assertion)
}

class ImageRestAssertion internal constructor(
    private val dsl: MockMvcResultMatchersDsl,
    private val jsonPrefix: String = "$"
) {

    fun url(expectedValue: String) {
        dsl.jsonPath("$jsonPrefix.url") { value(expectedValue) }
    }
//....
}

What do you think? It's a good idea to hide jsonPaths behins such dsl assertions?

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Well, I'm leaning toward yes. Hiding the jsonPath behind a more domain-specific DSL has several advantages:

Readability: Your custom DSL makes the tests more readable, as it describes the expected structure of the response in a more human-friendly way. This makes it easier for other developers to understand the test cases.

Maintainability: Using a custom DSL helps you encapsulate the structure of the response in one place. If the API response structure changes, you'll need to update your DSL assertions only, rather than updating multiple test cases individually.

Reusability: Your custom DSL is reusable across different test cases. This will lead to less code duplication and improved maintainability.

Test abstraction: By hiding the jsonPath details, your tests become more focused on the behavior and business requirements, rather than the implementation details of the API responses.

However, what I don't really like:

Learning curve: A custom DSL might introduce a learning curve for developers who are not familiar with it. They will need to understand the DSL before they can effectively write and update tests.

Complexity: Introducing a custom DSL adds an additional layer of abstraction, sometimes leading to increased complexity in your test codebase.

As long as you keep your DSL simple and well-documented, the benefits of increased readability, maintainability, and reusability will likely outweigh the potential trade-offs.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.