
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