All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.ch.tutteli.atrium.specs.verbs.VerbSpec.kt Maven / Gradle / Ivy

package ch.tutteli.atrium.specs.verbs

import ch.tutteli.atrium._core
import ch.tutteli.atrium.api.fluent.en_GB.*
import ch.tutteli.atrium.core.polyfills.fullName
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.creating.ExpectGrouping
import ch.tutteli.atrium.logic.changeSubject
import ch.tutteli.atrium.logic.creating.RootExpectBuilder
import ch.tutteli.atrium.reporting.reportables.ErrorMessages.*
import ch.tutteli.atrium.reporting.reportables.descriptions.DescriptionAnyProof
import ch.tutteli.atrium.reporting.reportables.descriptions.DescriptionComparableProof
import ch.tutteli.atrium.specs.*
import ch.tutteli.atrium.specs.integration.toContainToBeGreaterDescr
import ch.tutteli.atrium.specs.integration.toContainToBeLessThanDescr
import ch.tutteli.atrium.specs.integration.toContainToEqualDescr
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.Suite

abstract class VerbSpec(
    forNonNullable: Pair Expect>,
    forNonNullableCreator: Pair.() -> Unit) -> Expect>,
    forNullable: Pair Expect>,
    forThrowable: Pair Any?) -> Expect<() -> Any?>>,
    forGrouping: Pair Unit) -> ExpectGrouping>,
    createSubGroup: Pair Unit) -> ExpectGrouping>,
    createSubExpect: Pair Expect>,
    describePrefix: String = "[Atrium] "
) : Spek({

    fun prefixedDescribe(description: String, body: Suite.() -> Unit) {
        prefixedDescribeTemplate(describePrefix, description, body)
    }

    prefixedDescribe("assertion verb '${forNonNullable.first}' which immediately evaluates assertions") {
        val (_, assertionVerb) = forNonNullable

        testNonNullableSubject { assertionVerb(it) }
    }

    prefixedDescribe("assertion verb '${forNonNullable.first}' which lazily evaluates assertions") {
        val (_, assertionVerb) = forNonNullableCreator

        context("the assertions hold") {
            it("does not throw an exception") {
                assertionVerb(1) { toEqual(1) }
            }
            context("a subsequent assertion holds") {
                it("does not throw an exception") {
                    assertionVerb(1) { toEqual(1) }.toBeLessThan(2)
                }
            }
            context("a subsequent group of assertions hold") {
                it("does not throw an exception") {
                    assertionVerb(1) { toEqual(1) }.and { toBeLessThan(2) }
                }
            }
            context("a subsequent assertion fails") {
                it("throws an AssertionError") {
                    assert {
                        assertionVerb(1) { toEqual(1) }.toBeLessThan(1)
                    }.toThrow {
                        message {
                            toContainToBeLessThanDescr(1)
                            notToContain(toEqualDescr)
                        }
                    }
                }
            }

            context("multiple assertions of a subsequent group of assertion fails") {
                it("evaluates all assertions and then throws an AssertionError, reporting only failing") {
                    assert {
                        assertionVerb(1) { toEqual(1) }.and { toBeLessThan(0); toEqual(1); toBeGreaterThan(2) }
                    }.toThrow {
                        message {
                            toContain(": 1")
                            toContainToBeLessThanDescr(0)
                            toContainToBeGreaterDescr(2)
                            notToContain(toEqualDescr)
                        }
                    }
                }
            }
        }

        context("one assertion fails") {
            it("evaluates all assertions and then throws an AssertionError") {
                assert {
                    assertionVerb(1) {
                        toBeLessThan(0)
                        toBeGreaterThan(2)
                    }
                }.toThrow {
                    message {
                        toContain(": 1")
                        toContainToBeLessThanDescr(0)
                        toContainToBeGreaterDescr(2)
                    }
                }
            }
        }
    }

    prefixedDescribe("assertion verb '${forNullable.first}' which supports nullable subjects") {
        val (_, assertionVerb) = forNullable

        context("subject is null") {
            it("does not throw an exception when calling $toEqualDescr(`null`)") {
                assertionVerb(null).toEqual(null)
            }
            it("throws an AssertionError when calling notToEqualNull") {
                assert {
                    assertionVerb(null).notToEqualNull { toEqual(1) }
                }.toThrow {
                    message {
                        toContain(
                            ": null",
                            "$notToEqualNullButToBeInstanceOfDescr : Int",
                            "$indentX$explanatoryBulletPoint$toEqualDescr : 1"
                        )
                    }
                }
            }
        }
        context("subject is not null") {
            testNonNullableSubject { subject -> assertionVerb(subject)._core.changeSubject.unreported { it!! } }
        }
    }

    prefixedDescribe("assertion verb '${forThrowable.first}' which deals with exceptions") {
        val (_, assertionVerb) = forThrowable

        context("an IllegalArgumentException occurs") {
            it("does not throw an exception expecting an IllegalArgumentException") {
                assertionVerb {
                    throw IllegalArgumentException("hello")
                }.toThrow {
                    message.toEqual("hello")
                }
            }
            it("throws an AssertionError when expecting an UnsupportedOperationException") {
                assert {
                    assertionVerb {
                        throw IllegalArgumentException()
                    }.toThrow()
                }.toThrow {
                    messageToContain(
                        DescriptionAnyProof.TO_BE_AN_INSTANCE_OF.string,
                        IllegalArgumentException::class.fullName,
                        UnsupportedOperationException::class.fullName
                    )
                }
            }
        }
    }
    prefixedDescribe("assertion verb which creates an ${ExpectGrouping::class}") {
        val (_, assertionVerb) = forGrouping
        val (_, group) = createSubGroup
        val (_, expect) = createSubExpect
        context("no expect defined via ${createSubExpect.name}") {
            it("nothing defined throws and reports missing expect") {
                assert {
                    assertionVerb("group description") {}
                }.toThrow {
                    message {
                        toContain(
                            "group description :",
                            AT_LEAST_ONE_EXPECTATION_DEFINED.string + " : false",
                            FORGOT_DO_DEFINE_EXPECTATION.string,
                        )
                        notToContain(DEFAULT_HINT_AT_LEAST_ONE_EXPECTATION_DEFINED.string)
                    }
                }

            }
            it("only groups defined throws and reports each group with a missing expect") {
                assert {
                    assertionVerb("group description") {
                        group("without expect") {}

                        group("with expect") {
                            expect(2).toEqual(2)
                        }

                        group("another without") {}
                    }
                }.toThrow {
                    message {
                        toContain(
                            "without expect",
                            "another without"
                        )
                        toContain.exactly(2).values(
                            AT_LEAST_ONE_EXPECTATION_DEFINED.string + " : false",
                            FORGOT_DO_DEFINE_EXPECTATION.string,
                            DEFAULT_HINT_AT_LEAST_ONE_EXPECTATION_DEFINED.string
                        )
                        notToContain("with expect")
                    }
                }
            }
        }

        context("the first expect holds") {
            it("does not throw an exception") {
                assertionVerb("my lovely expectations") {
                    expect(1).toEqual(1)
                }
            }
            context("a subsequent expect holds") {
                it("does not throw an exception") {
                    assertionVerb("my lovely expectations") {
                        expect(1).toEqual(1)
                        expect(0).toBeLessThan(2)
                    }
                }
            }
            context("a subsequent group of expect hold") {
                it("does not throw an exception") {
                    assertionVerb("my lovely expectations") {
                        expect(1).toEqual(1)
                        group("some group") {
                            expect(1).toBeLessThan(2)
                        }
                    }
                }
            }
            context("a subsequent expect fails") {
                it("throws an AssertionError") {
                    assert {
                        assertionVerb("my lovely expectations") {
                            expect(1).toEqual(1)
                            expect(1).toBeLessThan(1)
                        }
                    }.toThrow {
                        message {
                            toContainToBeLessThanDescr(1)
                            notToContain(toEqualDescr)
                        }
                    }
                }
            }

            context("multiple subsequent expect/group fail") {
                it("evaluates all and then throws an AssertionError, reporting only failing") {
                    assert {
                        assertionVerb("my lovely expectations") {
                            expect(1).toEqual(1) // holds
                            expect(2).toEqual(3)
                            group("verifying Xy") {
                                expect(4).toBeLessThan(0)
                                expect(5).toEqual(6)
                                expect(7).toBeGreaterThan(1) // holds
                            }
                            expect(8).toEqual(9)
                        }
                    }.toThrow {
                        message {
                            toContain(": 2")
                            toContainToEqualDescr(3)
                            //TODO 1.3.0 should contain the grouping icon
                            toContain("verifying Xy : ")
                            toContain(": 4")
                            toContainToBeLessThanDescr(0)
                            toContain(": 5")
                            toContainToEqualDescr(6)
                            toContain(": 8")
                            toContainToEqualDescr(9)
                            notToContain.regex(
                                ": 1",
                                "$toEqualDescr\\s+: 1",
                                ": 7",
                                "${DescriptionComparableProof.TO_BE_GREATER_THAN.string}\\s+: 1",
                            )
                        }
                    }
                }
            }
        }

    }

})

private fun Suite.testNonNullableSubject(assertionVerb: (Int) -> Expect) {
    it("does not throw an exception in case the assertion holds") {
        assertionVerb(1).toEqual(1)
    }
    it("throws an AssertionError as soon as one expectation fails") {
        assert {
            assertionVerb(1).toBeLessThanOrEqualTo(10).and.toBeLessThanOrEqualTo(0).and.toBeGreaterThanOrEqualTo(2)
        }.toThrow {
            message{
                toContain(": 1")
                toContainRegex(DescriptionComparableProof.TO_BE_LESS_THAN_OR_EQUAL_TO.string+"\\s+: 0")
                notToContain.regex(DescriptionComparableProof.TO_BE_GREATER_THAN_OR_EQUAL_TO.string+"\\s+: 2")

            }
        }
    }
}


// does not make sense to test the verbs with the verbs themselves. Thus, we create our own assertion verb here
private fun  assert(act: () -> R): Expect<() -> R> =
    RootExpectBuilder.forSubject(act)
        .withVerb(DummyTranslatables.EXPECT_THROWN)
        .withoutOptions()
        .build()




© 2015 - 2025 Weber Informatics LLC | Privacy Policy