diff --git a/README.md b/README.md index c5c4639ed..950cded94 100644 --- a/README.md +++ b/README.md @@ -319,6 +319,7 @@ expect: 10 (kotlin.Int <1234789>) ◆ is greater than: 10 (kotlin.Int <1234789>) ``` + An assertion group throws an `AssertionError` at the end of its block; hence reports that both assertions do not hold. You can use `and` as filling element between single assertions and assertion group blocks: @@ -343,15 +344,16 @@ expect { ``` ↑ [Example](https://github.com/robstoll/atrium/tree/master/samples/readme-examples/src/main/kotlin/readme/examples/ReadmeSpec.kt#L67)Output ```text -expect the thrown exception: java.lang.IllegalArgumentException -◆ is instance of type: IllegalStateException (java.lang.IllegalStateException) - » Properties of the unexpected IllegalArgumentException - » message: "name is empty" <1234789> - » stacktrace: - ⚬ readme.examples.ReadmeSpec$1$4$1.invoke(ReadmeSpec.kt:70) - ⚬ readme.examples.ReadmeSpec$1$4$1.invoke(ReadmeSpec.kt:45) - ⚬ readme.examples.ReadmeSpec$1$4.invoke(ReadmeSpec.kt:627) - ⚬ readme.examples.ReadmeSpec$1$4.invoke(ReadmeSpec.kt:45) +expect: () -> kotlin.Nothing (readme.examples.ReadmeSpec$1$4$1 <1234789>) +◆ ▶ thrown exception when called: java.lang.IllegalArgumentException + ◾ is instance of type: IllegalStateException (java.lang.IllegalStateException) + » Properties of the unexpected IllegalArgumentException + » message: "name is empty" <1234789> + » stacktrace: + ⚬ readme.examples.ReadmeSpec$1$4$1.invoke(ReadmeSpec.kt:70) + ⚬ readme.examples.ReadmeSpec$1$4$1.invoke(ReadmeSpec.kt:45) + ⚬ readme.examples.ReadmeSpec$1$4.invoke(ReadmeSpec.kt:626) + ⚬ readme.examples.ReadmeSpec$1$4.invoke(ReadmeSpec.kt:45) ``` @@ -377,9 +379,10 @@ expect { ``` ↑ [Example](https://github.com/robstoll/atrium/tree/master/samples/readme-examples/src/main/kotlin/readme/examples/ReadmeSpec.kt#L74)Output ```text -expect the thrown exception: java.lang.IllegalArgumentException -◆ ▶ message: null - ◾ is instance of type: String (kotlin.String) -- Class: String (java.lang.String) +expect: () -> kotlin.Nothing (readme.examples.ReadmeSpec$1$5$1 <1234789>) +◆ ▶ thrown exception when called: java.lang.IllegalArgumentException + ◾ ▶ message: null + ◾ is instance of type: String (kotlin.String) -- Class: String (java.lang.String) ``` @@ -396,10 +399,11 @@ expect { ``` ↑ [Example](https://github.com/robstoll/atrium/tree/master/samples/readme-examples/src/main/kotlin/readme/examples/ReadmeSpec.kt#L80)Output ```text -expect the thrown exception: java.lang.IllegalArgumentException -◆ ▶ message: null - ◾ is instance of type: String (kotlin.String) -- Class: String (java.lang.String) - » starts with: "firstName" <1234789> +expect: () -> kotlin.Nothing (readme.examples.ReadmeSpec$1$6$1 <1234789>) +◆ ▶ thrown exception when called: java.lang.IllegalArgumentException + ◾ ▶ message: null + ◾ is instance of type: String (kotlin.String) -- Class: String (java.lang.String) + » starts with: "firstName" <1234789> ``` @@ -418,8 +422,8 @@ expect { ``` ↑ [Example](https://github.com/robstoll/atrium/tree/master/samples/readme-examples/src/main/kotlin/readme/examples/ReadmeSpec.kt#L88)Output ```text -expect the thrown exception: java.lang.IllegalArgumentException -◆ is: not thrown at all +expect: () -> kotlin.Nothing (readme.examples.ReadmeSpec$1$7$1 <1234789>) +◆ does not: throw when called » Properties of the unexpected IllegalArgumentException » message: "name is empty" <1234789> » stacktrace: @@ -1604,25 +1608,26 @@ expect { ``` ↑ [Example](https://github.com/robstoll/atrium/tree/master/samples/readme-examples/src/main/kotlin/readme/examples/ReadmeSpec.kt#L404)Output ```text -expect the thrown exception: java.lang.IllegalArgumentException -◆ is instance of type: IllegalStateException (java.lang.IllegalStateException) - » ▶ message: CANNOT evaluate representation as it is based on subject which is not defined. - » is instance of type: String (kotlin.String) -- Class: String (java.lang.String) - » contains: - ⚬ value: "no no no" <1234789> - ⚬ ▶ number of occurrences: -1 - ◾ is at least: 1 - » Properties of the unexpected IllegalArgumentException - » message: "no no no..." <1234789> - » stacktrace: - ⚬ readme.examples.ReadmeSpec2$1$31$1.invoke(ReadmeSpec.kt:409) - ⚬ readme.examples.ReadmeSpec2$1$31$1.invoke(ReadmeSpec.kt:221) - ⚬ readme.examples.ReadmeSpec2$1$31.invoke(ReadmeSpec.kt:627) - ⚬ readme.examples.ReadmeSpec2$1$31.invoke(ReadmeSpec.kt:221) - » cause: java.lang.UnsupportedOperationException - » message: "not supported" <1234789> +expect: () -> kotlin.Nothing (readme.examples.ReadmeSpec2$1$31$1 <1234789>) +◆ ▶ thrown exception when called: java.lang.IllegalArgumentException + ◾ is instance of type: IllegalStateException (java.lang.IllegalStateException) + » ▶ message: CANNOT evaluate representation as it is based on subject which is not defined. + » is instance of type: String (kotlin.String) -- Class: String (java.lang.String) + » contains: + ⚬ value: "no no no" <1234789> + ⚬ ▶ number of occurrences: -1 + ◾ is at least: 1 + » Properties of the unexpected IllegalArgumentException + » message: "no no no..." <1234789> » stacktrace: - ⚬ readme.examples.ReadmeSpec2$1$31$1.invoke(ReadmeSpec.kt:407) + ⚬ readme.examples.ReadmeSpec2$1$31$1.invoke(ReadmeSpec.kt:409) + ⚬ readme.examples.ReadmeSpec2$1$31$1.invoke(ReadmeSpec.kt:221) + ⚬ readme.examples.ReadmeSpec2$1$31.invoke(ReadmeSpec.kt:626) + ⚬ readme.examples.ReadmeSpec2$1$31.invoke(ReadmeSpec.kt:221) + » cause: java.lang.UnsupportedOperationException + » message: "not supported" <1234789> + » stacktrace: + ⚬ readme.examples.ReadmeSpec2$1$31$1.invoke(ReadmeSpec.kt:407) ``` diff --git a/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/fun0Assertions.kt b/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/fun0Assertions.kt new file mode 100644 index 000000000..cdc5d95b9 --- /dev/null +++ b/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/fun0Assertions.kt @@ -0,0 +1,63 @@ +package ch.tutteli.atrium.api.fluent.en_GB + +import ch.tutteli.atrium.creating.Expect +import ch.tutteli.atrium.domain.builders.ExpectImpl + +/** + * Expects that the thrown [Throwable] *is a* [TExpected] (the same type or a sub-type). + * + * Notice, that asserting a generic type is [flawed](https://youtrack.jetbrains.com/issue/KT-27826). + * For instance `toThrow>` would only check if the subject is a `MyException` without checking if + * the element type is actually `String`. + * + * @return An assertion container with the new type [TExpected]. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +inline fun Expect Any?>.toThrow(): Expect = + ExpectImpl.fun0.isThrowing(this, TExpected::class).getExpectOfFeature() + +/** + * Expects that the thrown [Throwable] *is a* [TExpected] (the same type or a sub-type) and + * that it holds all assertions the given [assertionCreator] creates. + * + * Notice, in contrast to other assertion functions which expect an [assertionCreator], this function returns not + * [Expect] of the initial type, which was `Throwable?` but an [Expect] of the specified type [TExpected]. + * This has the side effect that a subsequent call has only assertion functions available which are suited + * for [TExpected]. Since [Expect] is invariant it especially means that an assertion function which was not + * written in a generic way will not be available. Fixing such a function is easy (in most cases), + * you need to transform it into a generic from. Following an example: + * + * ``` + * interface Person + * class Student: Person + * fun Expect.foo() = "dummy" // limited only to Person, not recommended + * fun Expect.bar() = "dummy" // available to Person and all subtypes, the way to go + * fun Expect.baz() = "dummy" // specific only for Student, ok since closed class + * + * val p: Person = Student() + * expect(p) // subject of type Person + * .isA { ... } // subject now refined to Student + * .baz() // available via Student + * .foo() // not available to Student, only to Person, results in compilation error + * .bar() // available via T : Person + * ``` + * + * Notice, that asserting a generic type is [flawed](https://youtrack.jetbrains.com/issue/KT-27826). + * For instance `toThrow>` would only check if the subject is a `MyException` without checking if + * the element type is actually `String`. + * + * @return An assertion container with the new type [TExpected]. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +inline fun Expect Any?>.toThrow( + noinline assertionCreator: Expect.() -> Unit +): Expect = ExpectImpl.fun0.isThrowing(this, TExpected::class).addToFeature(assertionCreator) + + +/** + * Expects that no [Throwable] is thrown at all when calling the subject (a lambda with arity 0, i.e. without argument) + * and changes the subject of the assertion to the return value of type [R]. + * + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +fun R> Expect.notToThrow(): Expect = ExpectImpl.fun0.isNotThrowing(this).getExpectOfFeature() diff --git a/bundles/cc-de_CH-robstoll/atrium-cc-de_CH-robstoll-common/src/test/kotlin/SmokeTest.kt b/bundles/cc-de_CH-robstoll/atrium-cc-de_CH-robstoll-common/src/test/kotlin/SmokeTest.kt index eb46649eb..fa7e4ad2a 100644 --- a/bundles/cc-de_CH-robstoll/atrium-cc-de_CH-robstoll-common/src/test/kotlin/SmokeTest.kt +++ b/bundles/cc-de_CH-robstoll/atrium-cc-de_CH-robstoll-common/src/test/kotlin/SmokeTest.kt @@ -4,7 +4,6 @@ import ch.tutteli.atrium.api.cc.de_CH.messageEnthaelt import ch.tutteli.atrium.api.cc.de_CH.wirft import ch.tutteli.atrium.api.cc.de_CH.wirftNichts import ch.tutteli.atrium.api.fluent.en_GB.toBe -import ch.tutteli.atrium.api.verbs.assertThat import ch.tutteli.atrium.assertions.Assertion import ch.tutteli.atrium.creating.Assert import ch.tutteli.atrium.creating.AssertionPlant @@ -41,14 +40,14 @@ class SmokeTest { @Test fun assertAnExceptionOccurred() { - assertThat { + expect { throw IllegalArgumentException() }.wirft{} } @Test fun assertAnExceptionWithAMessageOccurred() { - assertThat { + expect { throw IllegalArgumentException("oho... hello btw") }.wirft { messageEnthaelt("hello") @@ -57,7 +56,7 @@ class SmokeTest { @Test fun assertNotToThrow() { - assertThat { + expect { }.wirftNichts() } diff --git a/bundles/cc-infix-en_GB-robstoll/atrium-cc-infix-en_GB-robstoll-common/src/test/kotlin/SmokeTest.kt b/bundles/cc-infix-en_GB-robstoll/atrium-cc-infix-en_GB-robstoll-common/src/test/kotlin/SmokeTest.kt index ab7a89265..0faa40592 100644 --- a/bundles/cc-infix-en_GB-robstoll/atrium-cc-infix-en_GB-robstoll-common/src/test/kotlin/SmokeTest.kt +++ b/bundles/cc-infix-en_GB-robstoll/atrium-cc-infix-en_GB-robstoll-common/src/test/kotlin/SmokeTest.kt @@ -1,5 +1,4 @@ import ch.tutteli.atrium.api.cc.infix.en_GB.* -import ch.tutteli.atrium.api.verbs.assertThat import ch.tutteli.atrium.assertions.Assertion import ch.tutteli.atrium.creating.Assert import ch.tutteli.atrium.creating.AssertionPlant @@ -28,14 +27,14 @@ class SmokeTest { @Test fun assertAnExceptionOccurred() { - assertThat { + assert { throw IllegalArgumentException() }.toThrow {} } @Test fun assertAnExceptionWithAMessageOccurred() { - assertThat { + assert { throw IllegalArgumentException("oho... hello btw") }.toThrow { o messageContains "hello" @@ -44,7 +43,7 @@ class SmokeTest { @Test fun assertNotToThrow() { - assertThat { + assert { }.notToThrow() } diff --git a/domain/api/atrium-domain-api-common/src/main/kotlin/ch/tutteli/atrium/domain/creating/Fun0Assertions.kt b/domain/api/atrium-domain-api-common/src/main/kotlin/ch/tutteli/atrium/domain/creating/Fun0Assertions.kt new file mode 100644 index 000000000..dca6bf4a7 --- /dev/null +++ b/domain/api/atrium-domain-api-common/src/main/kotlin/ch/tutteli/atrium/domain/creating/Fun0Assertions.kt @@ -0,0 +1,29 @@ +package ch.tutteli.atrium.domain.creating + +import ch.tutteli.atrium.core.polyfills.loadSingleService +import ch.tutteli.atrium.creating.Expect +import ch.tutteli.atrium.domain.creating.changers.ChangedSubjectPostStep +import kotlin.reflect.KClass + +/** + * The access point to an implementation of [Fun0Assertions]. + * + * It loads the implementation lazily via [loadSingleService]. + */ +val fun0Assertions by lazy { loadSingleService(Fun0Assertions::class) } + + +/** + * Defines the minimum set of assertion functions and builders applicable to lambdas with arity 0 + * (i.e. a lambda with 0 arguments or in other words `() -> R`), + * which an implementation of the domain of Atrium has to provide. + */ +interface Fun0Assertions { + + fun isThrowing( + assertionContainer: Expect Any?>, + expectedType: KClass + ): ChangedSubjectPostStep<*, TExpected> + + fun R> isNotThrowing(assertionContainer: Expect): ChangedSubjectPostStep<*, R> +} diff --git a/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/ExpectImpl.kt b/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/ExpectImpl.kt index 5ba290666..b94dce233 100644 --- a/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/ExpectImpl.kt +++ b/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/ExpectImpl.kt @@ -88,6 +88,12 @@ object ExpectImpl { */ inline val floatingPoint get() = FloatingPointAssertionsBuilder + /** + * Returns [Fun0AssertionsBuilder] - [Assertion]s applicable to lambdas with arity 0 + * which inter alia delegates to the implementation of [FloatingPointAssertions]. + */ + inline val fun0 get() = Fun0AssertionsBuilder + /** * Returns [IterableAssertionsBuilder]. * which inter alia delegates to the implementation of [IterableAssertions]. diff --git a/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/creating/Fun0AssertionsBuilder.kt b/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/creating/Fun0AssertionsBuilder.kt new file mode 100644 index 000000000..5b27584e0 --- /dev/null +++ b/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/creating/Fun0AssertionsBuilder.kt @@ -0,0 +1,25 @@ +@file:Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") + +package ch.tutteli.atrium.domain.builders.creating + +import ch.tutteli.atrium.core.polyfills.loadSingleService +import ch.tutteli.atrium.creating.Expect +import ch.tutteli.atrium.domain.creating.Fun0Assertions +import ch.tutteli.atrium.domain.creating.fun0Assertions +import kotlin.reflect.KClass + +/** + * Delegates inter alia to the implementation of [Fun0Assertions]. + * In detail, it implements [Fun0Assertions] by delegating to [fun0Assertions] + * which in turn delegates to the implementation via [loadSingleService]. + */ +object Fun0AssertionsBuilder : Fun0Assertions { + + override inline fun isThrowing( + assertionContainer: Expect Any?>, + expectedType: KClass + ) = fun0Assertions.isThrowing(assertionContainer, expectedType) + + override inline fun R> isNotThrowing(assertionContainer: Expect) = + fun0Assertions.isNotThrowing(assertionContainer) +} diff --git a/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/creating/NewFeatureAssertionsBuilder.kt b/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/creating/NewFeatureAssertionsBuilder.kt index 8f072a849..b76f5eae3 100644 --- a/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/creating/NewFeatureAssertionsBuilder.kt +++ b/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/creating/NewFeatureAssertionsBuilder.kt @@ -42,7 +42,7 @@ object NewFeatureAssertionsBuilder : NewFeatureAssertions { * Also, if the extraction of the feature is always safe, then you can just use one of the fN functions * (e.g. [f1] for a function expecting 1 argument) or [property]. */ - inline fun extractor(originalAssertionContainer: Expect) = + inline fun extractor(originalAssertionContainer: Expect): FeatureExtractorBuilder.DescriptionStep = FeatureExtractorBuilder.create(originalAssertionContainer) @@ -71,16 +71,16 @@ object NewFeatureAssertionsBuilder : NewFeatureAssertions { fun manualFeature( assertionContainer: Expect, - name: String, + description: String, provider: T.() -> R - ): ExtractedFeaturePostStep = extractFeature(assertionContainer, name, provider) + ): ExtractedFeaturePostStep = extractFeature(assertionContainer, description, provider) fun manualFeature( assertionContainer: Expect, - name: Translatable, + description: Translatable, provider: T.() -> R ): ExtractedFeaturePostStep = - genericFeature(assertionContainer, createMetaFeature(assertionContainer, name, provider)) + genericFeature(assertionContainer, createMetaFeature(assertionContainer, description, provider)) fun genericSubjectBasedFeature( assertionContainer: Expect, @@ -104,31 +104,31 @@ object NewFeatureAssertionsBuilder : NewFeatureAssertions { private fun extractFeature( assertionContainer: Expect, - name: String, + description: String, provider: (T) -> R ): ExtractedFeaturePostStep = - genericFeature(assertionContainer, createMetaFeature(assertionContainer, name, provider)) + genericFeature(assertionContainer, createMetaFeature(assertionContainer, description, provider)) private fun createMetaFeature( assertionContainer: Expect, - name: String, + description: String, provider: (T) -> R - ): MetaFeature = createMetaFeature(assertionContainer, Untranslatable(name), provider) + ): MetaFeature = createMetaFeature(assertionContainer, Untranslatable(description), provider) private fun createMetaFeature( assertionContainer: Expect, - name: Translatable, + description: Translatable, provider: (T) -> R ): MetaFeature { return assertionContainer.maybeSubject.fold({ MetaFeature( - name, + description, RawString.create(ErrorMessages.REPRESENTATION_BASED_ON_SUBJECT_NOT_DEFINED), None ) }) { val prop = provider(it) - MetaFeature(name, prop, Some(prop)) + MetaFeature(description, prop, Some(prop)) } } @@ -157,7 +157,8 @@ class MetaFeatureOption(private val expect: Expect) { //@formatter:off /** - * Creates a [MetaFeature] for the given function [f] without arguments => use [f0] in case of ambiguity issues. + * Creates a [MetaFeature] for the given function [f] without arguments => use [f0] in case of + * ambiguity issues. * * Notice for assertion function writers: you should use [ExpectImpl].[feature][ExpectImpl.feature] and pass a * class reference instead of using this convenience function (e.g. `ExpectImpl.feature(MyClass::fun)`). @@ -168,7 +169,8 @@ class MetaFeatureOption(private val expect: Expect) { f0(f) /** - * Creates a [MetaFeature] for the given function [f] which expects 1 argument => use [f1] in case of ambiguity issues. + * Creates a [MetaFeature] for the given function [f] which expects 1 argument => use [f1] in case of + * ambiguity issues. * * Notice for assertion function writers: you should use [ExpectImpl].[feature][ExpectImpl.feature] and pass a * class reference instead of using this convenience function (e.g. `ExpectImpl.feature(MyClass::fun, ...)`). @@ -179,7 +181,8 @@ class MetaFeatureOption(private val expect: Expect) { f1(f, a1) /** - * Creates a [MetaFeature] for the given function [f] which expects 2 arguments => use [f2] in case of ambiguity issues. + * Creates a [MetaFeature] for the given function [f] which expects 2 arguments => use [f2] in case of + * ambiguity issues. * * Notice for assertion function writers: you should use [ExpectImpl].[feature][ExpectImpl.feature] and pass a * class reference instead of using this convenience function (e.g. `ExpectImpl.feature(MyClass::fun, ...)`). @@ -190,7 +193,8 @@ class MetaFeatureOption(private val expect: Expect) { f2(f, a1, a2) /** - * Creates a [MetaFeature] for the given function [f] which expects 3 arguments => use [f3] in case of ambiguity issues. + * Creates a [MetaFeature] for the given function [f] which expects 3 arguments => use [f3] in case of + * ambiguity issues. * * Notice for assertion function writers: you should use [ExpectImpl].[feature][ExpectImpl.feature] and pass a * class reference instead of using this convenience function (e.g. `ExpectImpl.feature(MyClass::fun, ...)`). @@ -201,7 +205,8 @@ class MetaFeatureOption(private val expect: Expect) { f3(f, a1, a2, a3) /** - * Creates a [MetaFeature] for the given function [f] which expects 4 arguments => use [f4] in case of ambiguity issues. + * Creates a [MetaFeature] for the given function [f] which expects 4 arguments => use [f4] in case of + * ambiguity issues. * * Notice for assertion function writers: you should use [ExpectImpl].[feature][ExpectImpl.feature] and pass a * class reference instead of using this convenience function (e.g. `ExpectImpl.feature(MyClass::fun, ...)`). @@ -212,7 +217,8 @@ class MetaFeatureOption(private val expect: Expect) { f4(f, a1, a2, a3, a4) /** - * Creates a [MetaFeature] for the given function [f] which expects 5 arguments => use [f5] in case of ambiguity issues. + * Creates a [MetaFeature] for the given function [f] which expects 5 arguments => use [f5] in case of + * ambiguity issues. * * Notice for assertion function writers: you should use [ExpectImpl].[feature][ExpectImpl.feature] and pass a * class reference instead of using this convenience function (e.g. `ExpectImpl.feature(MyClass::fun, ...)`). diff --git a/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/creating/changers/FeatureExtractorBuilder.kt b/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/creating/changers/FeatureExtractorBuilder.kt index 4ae971cfe..37c9dd9f5 100644 --- a/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/creating/changers/FeatureExtractorBuilder.kt +++ b/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/creating/changers/FeatureExtractorBuilder.kt @@ -123,8 +123,7 @@ interface FeatureExtractorBuilder { /** * Step to define the feature extraction as such where a one can include a check by returning [None] in case the - * extraction should not be carried out - * to see whether the feature extraction is feasible or not. + * extraction should not be carried out. * * @param T the type of the current subject. */ diff --git a/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/creating/changers/SubjectChangerBuilder.kt b/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/creating/changers/SubjectChangerBuilder.kt index 4652451d1..58fb1e6b8 100644 --- a/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/creating/changers/SubjectChangerBuilder.kt +++ b/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/creating/changers/SubjectChangerBuilder.kt @@ -3,6 +3,9 @@ package ch.tutteli.atrium.domain.builders.creating.changers import ch.tutteli.atrium.assertions.DescriptiveAssertion +import ch.tutteli.atrium.core.None +import ch.tutteli.atrium.core.Option +import ch.tutteli.atrium.core.Some import ch.tutteli.atrium.core.polyfills.cast import ch.tutteli.atrium.creating.Assert import ch.tutteli.atrium.creating.AssertionPlantNullable @@ -17,9 +20,6 @@ import ch.tutteli.atrium.reporting.translating.Translatable import ch.tutteli.atrium.reporting.translating.Untranslatable import ch.tutteli.atrium.translations.DescriptionAnyAssertion import kotlin.reflect.KClass -import ch.tutteli.atrium.core.Some -import ch.tutteli.atrium.core.None -import ch.tutteli.atrium.core.Option /** * Defines the contract for sophisticated `change subject` processes. @@ -117,8 +117,8 @@ interface SubjectChangerBuilder { fun downCastTo(subType: KClass): FailureHandlerOption = withDescriptionAndRepresentation(DescriptionAnyAssertion.IS_A, subType) .withTransformation { - Option.someIf( subType.isInstance(it) ){ subType.cast(it) } - } + Option.someIf(subType.isInstance(it)) { subType.cast(it) } + } /** * Uses the given [description] and [representation] to represent the change by delegating to the other overload @@ -152,7 +152,7 @@ interface SubjectChangerBuilder { /** * Step to define the transformation which yields the new subject wrapped into a [Some] if the transformation - * as such can be carried out or [None]. + * as such can be carried out; otherwise [None]. * * @param T the type of the current subject. */ diff --git a/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/creating/changers/impl/subjectchanger/defaultImpls.kt b/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/creating/changers/impl/subjectchanger/defaultImpls.kt index 4be7c79e1..81697ef87 100644 --- a/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/creating/changers/impl/subjectchanger/defaultImpls.kt +++ b/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/creating/changers/impl/subjectchanger/defaultImpls.kt @@ -77,13 +77,13 @@ class FinalStepImpl( transformAndApply = { assertionCreator -> transformIt(this, Some(assertionCreator)) } ) - private fun transformIt(expect: Expect, subAssertions: Option.() -> Unit>) = + private fun transformIt(expect: Expect, maybeSubAssertions: Option.() -> Unit>) = subjectChanger.reported( expect, transformationStep.description, transformationStep.representation, transformation, failureHandler, - subAssertions + maybeSubAssertions ) } diff --git a/domain/builders/atrium-domain-builders-common/src/test/kotlin/ch/tutteli/atrium/domain/builders/creating/EitherSpec.kt b/domain/builders/atrium-domain-builders-common/src/test/kotlin/ch/tutteli/atrium/domain/builders/creating/EitherSpec.kt index 24c340113..c3a8a4a54 100644 --- a/domain/builders/atrium-domain-builders-common/src/test/kotlin/ch/tutteli/atrium/domain/builders/creating/EitherSpec.kt +++ b/domain/builders/atrium-domain-builders-common/src/test/kotlin/ch/tutteli/atrium/domain/builders/creating/EitherSpec.kt @@ -6,7 +6,6 @@ import ch.tutteli.atrium.api.fluent.en_GB.startsWith import ch.tutteli.atrium.api.fluent.en_GB.toThrow import ch.tutteli.atrium.api.verbs.internal.expect import ch.tutteli.atrium.core.None -import ch.tutteli.atrium.core.Option import ch.tutteli.atrium.core.Some import ch.tutteli.atrium.creating.Expect import ch.tutteli.atrium.domain.builders.ExpectImpl diff --git a/domain/robstoll-lib/atrium-domain-robstoll-lib-common/src/main/kotlin/ch/tutteli/atrium/domain/robstoll/lib/creating/fun0Assertions.kt b/domain/robstoll-lib/atrium-domain-robstoll-lib-common/src/main/kotlin/ch/tutteli/atrium/domain/robstoll/lib/creating/fun0Assertions.kt new file mode 100644 index 000000000..251d3ad34 --- /dev/null +++ b/domain/robstoll-lib/atrium-domain-robstoll-lib-common/src/main/kotlin/ch/tutteli/atrium/domain/robstoll/lib/creating/fun0Assertions.kt @@ -0,0 +1,94 @@ +package ch.tutteli.atrium.domain.robstoll.lib.creating + +import ch.tutteli.atrium.assertions.Assertion +import ch.tutteli.atrium.core.None +import ch.tutteli.atrium.core.Option +import ch.tutteli.atrium.core.Some +import ch.tutteli.atrium.creating.Expect +import ch.tutteli.atrium.domain.builders.ExpectImpl +import ch.tutteli.atrium.domain.creating.changers.ChangedSubjectPostStep +import ch.tutteli.atrium.domain.creating.changers.SubjectChanger +import ch.tutteli.atrium.domain.robstoll.lib.creating.throwable.thrown.creators.ThrowableThrownFailureHandler +import ch.tutteli.atrium.reporting.RawString +import ch.tutteli.atrium.reporting.reporter +import ch.tutteli.atrium.translations.DescriptionFunLikeAssertion.* +import kotlin.reflect.KClass + +fun _isThrowing( + assertionContainer: Expect Any?>, + expectedType: KClass +): ChangedSubjectPostStep<*, TExpected> = + //TODO allow to pass an ExpectOptions which allows to change the nullRepresentation. + ExpectImpl.feature + .manualFeature(assertionContainer, THROWN_EXCEPTION_WHEN_CALLED) { + catchAndAdjustThrowable(this).fold( + { it }, + { + // use null as subject in case no exception occurred + null + } + ) + } + .getExpectOfFeature() + .let { + ExpectImpl.changeSubject(it).reportBuilder() + .downCastTo(expectedType) + .withFailureHandler(ThrowableThrownFailureHandler(maxStackTrace = 7)) + .build() + } + +private inline fun catchAndAdjustThrowable(act: () -> R): Either = + try { + Right(act()) + } catch (throwable: Throwable) { + //TODO should be taken from current assertionContainer once it is configured this way + reporter.atriumErrorAdjuster.adjust(throwable) + Left(throwable) + } + +//TODO consider to move to core of Atrium +private sealed class Either { + + inline fun map(f: (R) -> T): Either = flatMap { Right(f(it)) } + + inline fun flatMap(f: (R) -> Either): Either = fold({ Left(it) }, f) + + inline fun fold(default: (Throwable) -> T, f: (R) -> T): T = when (this) { + is Right -> f(this.r) + is Left -> default(this.l) + } +} + +private data class Left(val l: Throwable) : Either() +private data class Right(val r: R) : Either() + +fun R> _isNotThrowing(assertionContainer: Expect): ChangedSubjectPostStep<*, R> { + return ExpectImpl.changeSubject(assertionContainer) + .unreported { + catchAndAdjustThrowable(it) + } + .let { eitherContainer -> + ExpectImpl.changeSubject(eitherContainer).reportBuilder() + .withDescriptionAndRepresentation(IS_NOT_THROWING_1, RawString.create(IS_NOT_THROWING_2)) + .withTransformation { + if (it is Right) Some(it.r) else None + } + //TODO could be extracted into an own pattern/function FailureHandlerAdapter + .withFailureHandler(object : SubjectChanger.FailureHandler, R> { + override fun createAssertion( + originalAssertionContainer: Expect>, + descriptiveAssertion: Assertion, + maybeAssertionCreator: Option.() -> Unit> + ): Assertion { + return ExpectImpl.changeSubject(originalAssertionContainer).unreported { (it as Left).l } + .let { + val handler = ThrowableThrownFailureHandler(maxStackTrace = 15) + handler.createAssertion(it, descriptiveAssertion, maybeAssertionCreator) + } + } + }) + .build() + } +} + + diff --git a/domain/robstoll/atrium-domain-robstoll-common/src/main/kotlin/ch/tutteli/atrium/domain/robstoll/creating/Fun0AssertionsImpl.kt b/domain/robstoll/atrium-domain-robstoll-common/src/main/kotlin/ch/tutteli/atrium/domain/robstoll/creating/Fun0AssertionsImpl.kt new file mode 100644 index 000000000..8501ae0ec --- /dev/null +++ b/domain/robstoll/atrium-domain-robstoll-common/src/main/kotlin/ch/tutteli/atrium/domain/robstoll/creating/Fun0AssertionsImpl.kt @@ -0,0 +1,18 @@ +package ch.tutteli.atrium.domain.robstoll.creating + +import ch.tutteli.atrium.creating.Expect +import ch.tutteli.atrium.domain.creating.Fun0Assertions +import ch.tutteli.atrium.domain.robstoll.lib.creating._isThrowing +import ch.tutteli.atrium.domain.robstoll.lib.creating._isNotThrowing +import kotlin.reflect.KClass + + +class Fun0AssertionsImpl : Fun0Assertions { + + override fun isThrowing( + assertionContainer: Expect Any?>, + expectedType: KClass + ) = _isThrowing(assertionContainer, expectedType) + + override fun R> isNotThrowing(assertionContainer: Expect) = _isNotThrowing(assertionContainer) +} diff --git a/domain/robstoll/atrium-domain-robstoll-js/src/main/kotlin/ch/tutteli/atrium/domain/robstoll/registerServices.kt b/domain/robstoll/atrium-domain-robstoll-js/src/main/kotlin/ch/tutteli/atrium/domain/robstoll/registerServices.kt index 3473df665..fb0414237 100644 --- a/domain/robstoll/atrium-domain-robstoll-js/src/main/kotlin/ch/tutteli/atrium/domain/robstoll/registerServices.kt +++ b/domain/robstoll/atrium-domain-robstoll-js/src/main/kotlin/ch/tutteli/atrium/domain/robstoll/registerServices.kt @@ -14,6 +14,7 @@ private val register = run { registerService { ch.tutteli.atrium.domain.robstoll.creating.ComparableAssertionsImpl() } registerService { ch.tutteli.atrium.domain.robstoll.creating.FeatureAssertionsImpl() } registerService { ch.tutteli.atrium.domain.robstoll.creating.FloatingPointAssertionsImpl() } + registerService { ch.tutteli.atrium.domain.robstoll.creating.Fun0AssertionsImpl() } registerService { ch.tutteli.atrium.domain.robstoll.creating.IterableAssertionsImpl() } registerService { ch.tutteli.atrium.domain.robstoll.creating.ListAssertionsImpl() } registerService { ch.tutteli.atrium.domain.robstoll.creating.MapAssertionsImpl() } diff --git a/domain/robstoll/atrium-domain-robstoll-jvm/src/main/resources/META-INF/services/ch.tutteli.atrium.domain.creating.Fun0Assertions b/domain/robstoll/atrium-domain-robstoll-jvm/src/main/resources/META-INF/services/ch.tutteli.atrium.domain.creating.Fun0Assertions new file mode 100644 index 000000000..827dc57b2 --- /dev/null +++ b/domain/robstoll/atrium-domain-robstoll-jvm/src/main/resources/META-INF/services/ch.tutteli.atrium.domain.creating.Fun0Assertions @@ -0,0 +1 @@ +ch.tutteli.atrium.domain.robstoll.creating.Fun0AssertionsImpl diff --git a/domain/robstoll/atrium-domain-robstoll-jvm/src/module/module-info.java b/domain/robstoll/atrium-domain-robstoll-jvm/src/module/module-info.java index 8a1f66a41..8eb9431c2 100644 --- a/domain/robstoll/atrium-domain-robstoll-jvm/src/module/module-info.java +++ b/domain/robstoll/atrium-domain-robstoll-jvm/src/module/module-info.java @@ -54,6 +54,9 @@ module ch.tutteli.atrium.domain.robstoll { provides ch.tutteli.atrium.domain.creating.FeatureAssertions with ch.tutteli.atrium.domain.robstoll.creating.FeatureAssertionsImpl; + provides ch.tutteli.atrium.domain.creating.Fun0Assertions + with ch.tutteli.atrium.domain.robstoll.creating.Fun0AssertionsImpl; + provides ch.tutteli.atrium.domain.creating.feature.extract.creators.FeatureExtractorCreatorFactory with ch.tutteli.atrium.domain.robstoll.creating.feature.extract.creators.FeatureExtractorCreatorFactoryImpl; diff --git a/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/creating/ReportingAssertionContainerSpec.kt b/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/creating/ReportingAssertionContainerSpec.kt index a73bc8cd5..403187795 100644 --- a/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/creating/ReportingAssertionContainerSpec.kt +++ b/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/creating/ReportingAssertionContainerSpec.kt @@ -104,7 +104,7 @@ abstract class ReportingAssertionContainerSpec( context("in case of assertion which fails") { context("throws an AssertionError") { - fun expectFun(): ThrowableThrown.Builder { + fun expectFun(): Expect<() -> Any?> { val testee = createTestee() return expect { testee.failingFun() diff --git a/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/integration/ThrowableAssertionsSpec.kt b/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/integration/ThrowableAssertionsSpec.kt index 929b14b28..507a5e5bb 100644 --- a/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/integration/ThrowableAssertionsSpec.kt +++ b/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/integration/ThrowableAssertionsSpec.kt @@ -4,6 +4,7 @@ package ch.tutteli.atrium.specs.integration import ch.tutteli.atrium.api.fluent.en_GB.* import ch.tutteli.atrium.api.verbs.internal.expect +import ch.tutteli.atrium.api.verbs.internal.expectOld import ch.tutteli.atrium.core.polyfills.fullName import ch.tutteli.atrium.creating.Expect import ch.tutteli.atrium.domain.creating.throwable.thrown.ThrowableThrown @@ -60,7 +61,7 @@ abstract class ThrowableAssertionsSpec( pattern: String, vararg otherPatterns: String ) { expect { - expect { ({ throw throwable })() }.toThrowFun() + expectOld { ({ throw throwable })() }.toThrowFun() }.toThrow { message { containsRegex(pattern, *otherPatterns) } } @@ -82,7 +83,7 @@ abstract class ThrowableAssertionsSpec( describeFun(toThrow) { checkToThrow("it throws an AssertionError when no exception occurs", { doToThrow -> expect { - expect { ({ /* no exception occurs */ })() }.doToThrow() + expectOld { ({ /* no exception occurs */ })() }.doToThrow() }.toThrow { message { contains.exactly(1).regex( @@ -97,7 +98,7 @@ abstract class ThrowableAssertionsSpec( checkToThrow( "it allows to define assertions for the Throwable if the correct exception is thrown", { toThrowWithCheck -> - expect { + expectOld { throw IllegalArgumentException("hello") }.toThrowWithCheck() }, @@ -146,7 +147,7 @@ abstract class ThrowableAssertionsSpec( it("throws if no assertion is made") { expect { - expect { + expectOld { throw IllegalArgumentException("hello") }.toThrowFunLazy { } }.toThrow { @@ -163,7 +164,7 @@ abstract class ThrowableAssertionsSpec( val notToThrowFun = notToThrow.lambda context("no exception occurs") { it("does not throw") { - expect {}.notToThrowFun() + expectOld {}.notToThrowFun() } } context("exception is thrown") { diff --git a/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/verbs/VerbSpec.kt b/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/verbs/VerbSpec.kt index 52e45a5cd..339de8e67 100644 --- a/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/verbs/VerbSpec.kt +++ b/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/verbs/VerbSpec.kt @@ -8,7 +8,6 @@ import ch.tutteli.atrium.domain.builders.AssertImpl import ch.tutteli.atrium.domain.builders.ExpectImpl import ch.tutteli.atrium.domain.builders.reporting.ExpectOptions import ch.tutteli.atrium.domain.builders.reporting.ReporterBuilder -import ch.tutteli.atrium.domain.creating.throwable.thrown.ThrowableThrown import ch.tutteli.atrium.reporting.RawString import ch.tutteli.atrium.reporting.Reporter import ch.tutteli.atrium.reporting.translating.Untranslatable @@ -25,7 +24,7 @@ abstract class VerbSpec( forNonNullable: Pair Expect>, forNonNullableCreator: Pair.() -> Unit) -> Expect>, forNullable: Pair Expect>, - forThrowable: Pair Unit) -> ThrowableThrown.Builder>, + forThrowable: Pair Any?, ExpectOptions, representation: String?) -> Expect<() -> Any?>>, describePrefix: String = "[Atrium] " ) : Spek({ @@ -178,8 +177,11 @@ abstract class VerbSpec( } prefixedDescribe("assertion verb '${forThrowable.first}' which deals with exceptions") { + val (_, assertionVerbFun) = forThrowable + fun assertionVerb(options: ExpectOptions = ExpectOptions(), representation: String? = null, act: () -> Any?) = + assertionVerbFun(act, options, representation) + context("an IllegalArgumentException occurs") { - val (_, assertionVerb) = forThrowable it("does not throw an exception expecting an IllegalArgumentException") { assertionVerb { throw IllegalArgumentException("hello") @@ -201,6 +203,8 @@ abstract class VerbSpec( } } } + + // customisations are not checked as it is assumed that this verb delegates to the verb forNonNullable } }) diff --git a/misc/specs/atrium-specs-jvm/src/main/kotlin/ch/tutteli/atrium/specs/integration/ThrowableAssertionsJvmSpec.kt b/misc/specs/atrium-specs-jvm/src/main/kotlin/ch/tutteli/atrium/specs/integration/ThrowableAssertionsJvmSpec.kt index 34a7e3476..117a86da3 100644 --- a/misc/specs/atrium-specs-jvm/src/main/kotlin/ch/tutteli/atrium/specs/integration/ThrowableAssertionsJvmSpec.kt +++ b/misc/specs/atrium-specs-jvm/src/main/kotlin/ch/tutteli/atrium/specs/integration/ThrowableAssertionsJvmSpec.kt @@ -7,6 +7,7 @@ import ch.tutteli.atrium.api.fluent.en_GB.message import ch.tutteli.atrium.api.fluent.en_GB.toBe import ch.tutteli.atrium.api.fluent.en_GB.toThrow import ch.tutteli.atrium.api.verbs.internal.expect +import ch.tutteli.atrium.api.verbs.internal.expectOld import ch.tutteli.atrium.core.polyfills.fullName import ch.tutteli.atrium.creating.Expect import ch.tutteli.atrium.domain.creating.throwable.thrown.ThrowableThrown @@ -50,7 +51,7 @@ abstract class ThrowableAssertionsJvmSpec( pattern: String, vararg otherPatterns: String ) { expect { - expect { ({ throw throwable })() }.toThrowFun() + expectOld { ({ throw throwable })() }.toThrowFun() }.toThrow { message { containsRegex(pattern, *otherPatterns) diff --git a/misc/verbs-internal/atrium-verbs-internal-common/src/main/kotlin/ch.tutteli.atrium.api.verbs.internal/atriumVerbs.kt b/misc/verbs-internal/atrium-verbs-internal-common/src/main/kotlin/ch.tutteli.atrium.api.verbs.internal/atriumVerbs.kt index bf87ac04d..a4a060a2b 100644 --- a/misc/verbs-internal/atrium-verbs-internal-common/src/main/kotlin/ch.tutteli.atrium.api.verbs.internal/atriumVerbs.kt +++ b/misc/verbs-internal/atrium-verbs-internal-common/src/main/kotlin/ch.tutteli.atrium.api.verbs.internal/atriumVerbs.kt @@ -18,25 +18,35 @@ import ch.tutteli.atrium.reporting.translating.StringBasedTranslatable /** * Creates an [Expect] for the given [subject]. * + * @param subject The subject for which we are going to postulate assertions. + * @param representation Optional, use it in case you want to use a custom representation for the subject. + * @param options Optional, use it in case you want to tweak the resulting [Expect], for instance, use another reporter. + * * @return The newly created assertion container. + * @throws AssertionError in case an assertion does not hold. */ -fun expect(subject: T, representation: String? = null, options: ExpectOptions = ExpectOptions()): Expect = +fun expect(subject: T, representation: String? = null, options: ExpectOptions? = null): Expect = ExpectBuilder.forSubject(subject) .withVerb(EXPECT) - .withOptions(options.merge(ExpectOptions(representation = representation?.let { RawString.create(it) }))) + .withMaybeRepresentationAndMaybeOptions(representation, options) .build() /** * Creates an [Expect] for the given [subject] and [Expect.addAssertionsCreatedBy] the - * given [assertionCreator] lambda where the created [Assertion]s are added as a group and usually (depending on - * the configured [Reporter]) reported as a whole. + * given [assertionCreator]-lambda where the created [Assertion]s are added as a group and reported as a whole. + * + * @param subject The subject for which we are going to postulate assertions. + * @param representation Optional, use it in case you want to use a custom representation for the subject. + * @param options Optional, use it in case you want to tweak the resulting [Expect], for instance, use another reporter. + * @param assertionCreator Assertion group block with a non-fail fast behaviour. * * @return The newly created assertion container. + * @throws AssertionError in case an assertion does not hold. */ fun expect( subject: T, representation: String? = null, - options: ExpectOptions = ExpectOptions(), + options: ExpectOptions? = null, assertionCreator: Expect.() -> Unit ): Expect = expect(subject, representation, options).addAssertionsCreatedBy(assertionCreator) @@ -46,7 +56,40 @@ fun expect( * * @return The newly created [ThrowableThrown.Builder]. */ -fun expect(act: () -> Unit): ThrowableThrown.Builder = ExpectImpl.throwable.thrownBuilder(EXPECT_THROWN, act, reporter) +fun expectOld(act: () -> Unit): ThrowableThrown.Builder = + ExpectImpl.throwable.thrownBuilder(EXPECT_THROWN, act, reporter) + +/** + * Creates an [Expect] with the given [act]-lambda as subject. + * + * @param act the subject for which we are going to postulate assertions. + * @param options Optional, use it in case you want to tweak the resulting [Expect], for instance, use another reporter. + * @param representation Optional, use it in case you want to use a custom representation for the subject. + * + * @return The newly created assertion container. + * @throws AssertionError in case an assertion does not hold. + */ +fun expect( + options: ExpectOptions? = null, + representation: String? = null, + act: () -> R +): Expect<() -> R> = expect(act, representation, options) + +/** + * Optimised version which only creates ExpectOptions if really required. + */ +fun ExpectBuilder.OptionsStep.withMaybeRepresentationAndMaybeOptions( + representation: String?, options: ExpectOptions? +): ExpectBuilder.FinalStep = + if (representation == null) { + if (options == null) this.withoutOptions() + else this.withOptions(options) + } else { + val representationOption = ExpectOptions(representation = RawString.create(representation)) + if (options == null) this.withOptions(representationOption) + else this.withOptions(options.merge(representationOption)) + } + enum class AssertionVerb(override val value: String) : StringBasedTranslatable { EXPECT("expect"), diff --git a/misc/verbs-internal/atrium-verbs-internal-common/src/test/kotlin/ch/tutteli/atrium/api/verbs/internal/VerbSpec.kt b/misc/verbs-internal/atrium-verbs-internal-common/src/test/kotlin/ch/tutteli/atrium/api/verbs/internal/VerbSpec.kt index f4278d9c8..5ec55814b 100644 --- a/misc/verbs-internal/atrium-verbs-internal-common/src/test/kotlin/ch/tutteli/atrium/api/verbs/internal/VerbSpec.kt +++ b/misc/verbs-internal/atrium-verbs-internal-common/src/test/kotlin/ch/tutteli/atrium/api/verbs/internal/VerbSpec.kt @@ -8,5 +8,5 @@ object ExpectSpec : VerbSpec( expect(subject, representation, options, assertionCreator) }, "expect" to { subject: Int?, representation, options -> expect(subject, representation, options) }, - "expect" to { act -> expect { act() } } + "expect" to { act: () -> Any?, options, representation -> expect(options, representation, { act() }) } ) diff --git a/misc/verbs/atrium-verbs-common/src/main/kotlin/ch/tutteli/atrium/api.verbs/AssertionVerb.kt b/misc/verbs/atrium-verbs-common/src/main/kotlin/ch/tutteli/atrium/api.verbs/AssertionVerb.kt index efbccfe86..61429f0a0 100644 --- a/misc/verbs/atrium-verbs-common/src/main/kotlin/ch/tutteli/atrium/api.verbs/AssertionVerb.kt +++ b/misc/verbs/atrium-verbs-common/src/main/kotlin/ch/tutteli/atrium/api.verbs/AssertionVerb.kt @@ -8,9 +8,6 @@ import ch.tutteli.atrium.reporting.translating.Translatable */ enum class AssertionVerb(override val value: String) : StringBasedTranslatable { ASSERT("assert"), - ASSERT_THROWN("assert the thrown exception"), ASSERT_THAT("assert that"), - ASSERT_THAT_THROWN("assert that the thrown exception"), EXPECT("expect"), - EXPECT_THROWN("expect the thrown exception"), } diff --git a/misc/verbs/atrium-verbs-common/src/main/kotlin/ch/tutteli/atrium/api.verbs/assert.kt b/misc/verbs/atrium-verbs-common/src/main/kotlin/ch/tutteli/atrium/api.verbs/assert.kt index 8919ad2f2..af1e18514 100644 --- a/misc/verbs/atrium-verbs-common/src/main/kotlin/ch/tutteli/atrium/api.verbs/assert.kt +++ b/misc/verbs/atrium-verbs-common/src/main/kotlin/ch/tutteli/atrium/api.verbs/assert.kt @@ -1,14 +1,11 @@ package ch.tutteli.atrium.api.verbs import ch.tutteli.atrium.api.verbs.AssertionVerb.ASSERT -import ch.tutteli.atrium.api.verbs.AssertionVerb.ASSERT_THROWN import ch.tutteli.atrium.assertions.Assertion import ch.tutteli.atrium.creating.Expect import ch.tutteli.atrium.domain.builders.ExpectImpl import ch.tutteli.atrium.domain.builders.reporting.ExpectBuilder import ch.tutteli.atrium.domain.builders.reporting.ExpectOptions -import ch.tutteli.atrium.domain.creating.throwable.thrown.ThrowableThrown -import ch.tutteli.atrium.reporting.reporter /** * Creates an [Expect] for the given [subject]. @@ -16,8 +13,9 @@ import ch.tutteli.atrium.reporting.reporter * @param subject The subject for which we are going to postulate assertions. * @param representation Optional, use it in case you want to use a custom representation for the subject. * @param options Optional, use it in case you want to tweak the resulting [Expect], for instance, use another reporter. + * * @return The newly created assertion container. - * @throws AssertionError in case an assertion does not hold + * @throws AssertionError in case an assertion does not hold. */ fun assert(subject: T, representation: String? = null, options: ExpectOptions? = null): Expect = ExpectBuilder.forSubject(subject) @@ -32,9 +30,10 @@ fun assert(subject: T, representation: String? = null, options: ExpectOption * @param subject The subject for which we are going to postulate assertions. * @param representation Optional, use it in case you want to use a custom representation for the subject. * @param options Optional, use it in case you want to tweak the resulting [Expect], for instance, use another reporter. + * * @param assertionCreator Assertion group block with a non-fail fast behaviour. * @return The newly created assertion container. - * @throws AssertionError in case an assertion does not hold + * @throws AssertionError in case an assertion does not hold. */ fun assert( subject: T, @@ -44,12 +43,20 @@ fun assert( ): Expect = assert(subject, representation, options).addAssertionsCreatedBy(assertionCreator) /** - * Creates a [ThrowableThrown.Builder] for the given function [act] which catches a potentially thrown [Throwable] - * and allows to define an assertion for it. + * Creates an [Expect] with the given [act]-lambda as subject. * - * @return The newly created [ThrowableThrown.Builder]. + * @param act the subject for which we are going to postulate assertions. + * @param options Optional, use it in case you want to tweak the resulting [Expect], for instance, use another reporter. + * + * @param representation Optional, use it in case you want to use a custom representation for the subject. + * @return The newly created assertion container. + * @throws AssertionError in case an assertion does not hold. */ -fun assert(act: () -> Unit): ThrowableThrown.Builder = ExpectImpl.throwable.thrownBuilder(ASSERT_THROWN, act, reporter) +fun assert( + options: ExpectOptions? = null, + representation: String? = null, + act: () -> R +): Expect<() -> R> = assert(act, representation, options) @Deprecated( "`assert` should not be nested, use `feature` instead.", diff --git a/misc/verbs/atrium-verbs-common/src/main/kotlin/ch/tutteli/atrium/api.verbs/assertThat.kt b/misc/verbs/atrium-verbs-common/src/main/kotlin/ch/tutteli/atrium/api.verbs/assertThat.kt index 5e5601eca..9b56135e9 100644 --- a/misc/verbs/atrium-verbs-common/src/main/kotlin/ch/tutteli/atrium/api.verbs/assertThat.kt +++ b/misc/verbs/atrium-verbs-common/src/main/kotlin/ch/tutteli/atrium/api.verbs/assertThat.kt @@ -1,14 +1,11 @@ package ch.tutteli.atrium.api.verbs import ch.tutteli.atrium.api.verbs.AssertionVerb.ASSERT_THAT -import ch.tutteli.atrium.api.verbs.AssertionVerb.ASSERT_THAT_THROWN import ch.tutteli.atrium.assertions.Assertion import ch.tutteli.atrium.creating.Expect import ch.tutteli.atrium.domain.builders.ExpectImpl import ch.tutteli.atrium.domain.builders.reporting.ExpectBuilder import ch.tutteli.atrium.domain.builders.reporting.ExpectOptions -import ch.tutteli.atrium.domain.creating.throwable.thrown.ThrowableThrown -import ch.tutteli.atrium.reporting.reporter /** * Creates an [Expect] for the given [subject]. @@ -16,8 +13,9 @@ import ch.tutteli.atrium.reporting.reporter * @param subject The subject for which we are going to postulate assertions. * @param representation Optional, use it in case you want to use a custom representation for the subject. * @param options Optional, use it in case you want to tweak the resulting [Expect], for instance, use another reporter. + * * @return The newly created assertion container. - * @throws AssertionError in case an assertion does not hold + * @throws AssertionError in case an assertion does not hold. */ fun assertThat(subject: T, representation: String? = null, options: ExpectOptions? = null): Expect = ExpectBuilder.forSubject(subject) @@ -32,9 +30,10 @@ fun assertThat(subject: T, representation: String? = null, options: ExpectOp * @param subject The subject for which we are going to postulate assertions. * @param representation Optional, use it in case you want to use a custom representation for the subject. * @param options Optional, use it in case you want to tweak the resulting [Expect], for instance, use another reporter. + * * @param assertionCreator Assertion group block with a non-fail fast behaviour. * @return The newly created assertion container. - * @throws AssertionError in case an assertion does not hold + * @throws AssertionError in case an assertion does not hold. */ fun assertThat( subject: T, @@ -44,13 +43,20 @@ fun assertThat( ): Expect = assertThat(subject, representation, options).addAssertionsCreatedBy(assertionCreator) /** - * Creates a [ThrowableThrown.Builder] for the given function [act] which catches a potentially thrown [Throwable] - * and allows to define an assertion for it. + * Creates an [Expect] with the given [act]-lambda as subject. * - * @return The newly created [ThrowableThrown.Builder]. + * @param act the subject for which we are going to postulate assertions. + * @param options Optional, use it in case you want to tweak the resulting [Expect], for instance, use another reporter. + * + * @param representation Optional, use it in case you want to use a custom representation for the subject. + * @return The newly created assertion container. + * @throws AssertionError in case an assertion does not hold. */ -fun assertThat(act: () -> Unit): ThrowableThrown.Builder = - ExpectImpl.throwable.thrownBuilder(ASSERT_THAT_THROWN, act, reporter) +fun assertThat( + options: ExpectOptions? = null, + representation: String? = null, + act: () -> R +): Expect<() -> R> = assertThat(act, representation, options) @Deprecated( "`assertThat` should not be nested, use `feature` instead.", diff --git a/misc/verbs/atrium-verbs-common/src/main/kotlin/ch/tutteli/atrium/api.verbs/expect.kt b/misc/verbs/atrium-verbs-common/src/main/kotlin/ch/tutteli/atrium/api.verbs/expect.kt index bbff8327c..6877a7c85 100644 --- a/misc/verbs/atrium-verbs-common/src/main/kotlin/ch/tutteli/atrium/api.verbs/expect.kt +++ b/misc/verbs/atrium-verbs-common/src/main/kotlin/ch/tutteli/atrium/api.verbs/expect.kt @@ -1,14 +1,11 @@ package ch.tutteli.atrium.api.verbs import ch.tutteli.atrium.api.verbs.AssertionVerb.EXPECT -import ch.tutteli.atrium.api.verbs.AssertionVerb.EXPECT_THROWN import ch.tutteli.atrium.assertions.Assertion import ch.tutteli.atrium.creating.Expect import ch.tutteli.atrium.domain.builders.ExpectImpl import ch.tutteli.atrium.domain.builders.reporting.ExpectBuilder import ch.tutteli.atrium.domain.builders.reporting.ExpectOptions -import ch.tutteli.atrium.domain.creating.throwable.thrown.ThrowableThrown -import ch.tutteli.atrium.reporting.reporter /** * Creates an [Expect] for the given [subject]. @@ -16,8 +13,9 @@ import ch.tutteli.atrium.reporting.reporter * @param subject The subject for which we are going to postulate assertions. * @param representation Optional, use it in case you want to use a custom representation for the subject. * @param options Optional, use it in case you want to tweak the resulting [Expect], for instance, use another reporter. + * * @return The newly created assertion container. - * @throws AssertionError in case an assertion does not hold + * @throws AssertionError in case an assertion does not hold. */ fun expect(subject: T, representation: String? = null, options: ExpectOptions? = null): Expect = ExpectBuilder.forSubject(subject) @@ -32,9 +30,10 @@ fun expect(subject: T, representation: String? = null, options: ExpectOption * @param subject The subject for which we are going to postulate assertions. * @param representation Optional, use it in case you want to use a custom representation for the subject. * @param options Optional, use it in case you want to tweak the resulting [Expect], for instance, use another reporter. + * * @param assertionCreator Assertion group block with a non-fail fast behaviour. * @return The newly created assertion container. - * @throws AssertionError in case an assertion does not hold + * @throws AssertionError in case an assertion does not hold. */ fun expect( subject: T, @@ -44,22 +43,21 @@ fun expect( ): Expect = expect(subject, representation, options).addAssertionsCreatedBy(assertionCreator) /** - * Creates a [ThrowableThrown.Builder] for the given function [act] which catches a potentially thrown [Throwable] - * and allows to define an assertion for it. + * Creates an [Expect] with the given [act]-lambda as subject. * - * @return The newly created [ThrowableThrown.Builder]. + * @param act the subject for which we are going to postulate assertions. + * @param options Optional, use it in case you want to tweak the resulting [Expect], for instance, use another reporter. + * @param representation Optional, use it in case you want to use a custom representation for the subject. + * + * @return The newly created assertion container. + * @throws AssertionError in case an assertion does not hold. */ -fun expect(act: () -> Unit): ThrowableThrown.Builder = ExpectImpl.throwable.thrownBuilder(EXPECT_THROWN, act, reporter) - -// TODO #97 now we are almost there to implement expect{}.toThrow in terms of expect -//fun expect( -// representation: String? = null, -// options: ExpectOptions? = null, -// act: () -> R -//): Expect<() -> R> = expect(act, representation, ExpectOptions { -// withVerb(EXPECT_THROWN) -// withNullRepresentation(RawString.create("no exception occurred")) -//}.merge(options)) +//note the order of the parameters (options before representation) is this way to disambiguate calls +fun expect( + options: ExpectOptions? = null, + representation: String? = null, + act: () -> R +): Expect<() -> R> = expect(act, representation, options) @Deprecated( "`expect` should not be nested, use `feature` instead.", diff --git a/misc/verbs/atrium-verbs-common/src/test/kotlin/ch/tutteli/atrium/api/verbs/VerbSpec.kt b/misc/verbs/atrium-verbs-common/src/test/kotlin/ch/tutteli/atrium/api/verbs/VerbSpec.kt index 7ba4c7394..a4157d056 100644 --- a/misc/verbs/atrium-verbs-common/src/test/kotlin/ch/tutteli/atrium/api/verbs/VerbSpec.kt +++ b/misc/verbs/atrium-verbs-common/src/test/kotlin/ch/tutteli/atrium/api/verbs/VerbSpec.kt @@ -8,7 +8,8 @@ object AssertSpec : VerbSpec( assert(subject, representation, options, assertionCreator) }, "assert" to { subject: Int?, representation, options -> assert(subject, representation, options) }, - "assert" to { act -> assert { act() } }) + "assert" to { act: () -> Any?, options, representation -> assert(options, representation, { act() }) } +) object AssertThatSpec : VerbSpec( "assertThat" to { subject: Int, representation, options -> assertThat(subject, representation, options) }, @@ -16,7 +17,8 @@ object AssertThatSpec : VerbSpec( assertThat(subject, representation, options, assertionCreator) }, "assertThat" to { subject: Int?, representation, options -> assertThat(subject, representation, options) }, - "assertThat" to { act -> assertThat { act() } }) + "assertThat" to { act: () -> Any?, options, representation -> assertThat(options, representation, { act() }) } +) object ExpectSpec : VerbSpec( "expect" to { subject: Int, representation, options -> expect(subject, representation, options) }, @@ -24,5 +26,5 @@ object ExpectSpec : VerbSpec( expect(subject, representation, options, assertionCreator) }, "expect" to { subject: Int?, representation, options -> expect(subject, representation, options) }, - "expect" to { act -> expect { act() } }) - + "expect" to { act: () -> Any?, options, representation -> expect(options, representation, { act() }) } +) diff --git a/translations/de_CH/atrium-translations-de_CH-common/src/main/kotlin/ch/tutteli/atrium/translations/DescriptionFunLikeAssertion.kt b/translations/de_CH/atrium-translations-de_CH-common/src/main/kotlin/ch/tutteli/atrium/translations/DescriptionFunLikeAssertion.kt new file mode 100644 index 000000000..38061b7ed --- /dev/null +++ b/translations/de_CH/atrium-translations-de_CH-common/src/main/kotlin/ch/tutteli/atrium/translations/DescriptionFunLikeAssertion.kt @@ -0,0 +1,13 @@ +package ch.tutteli.atrium.translations + +import ch.tutteli.atrium.assertions.DescriptiveAssertion +import ch.tutteli.atrium.reporting.translating.StringBasedTranslatable + +/** + * Contains the [DescriptiveAssertion.description]s of the assertion functions which are applicable to [Any]. + */ +enum class DescriptionFunLikeAssertion(override val value: String) : StringBasedTranslatable { + IS_NOT_THROWING_1("wirft"), + IS_NOT_THROWING_2("keine Exception bei Aufruf"), + THROWN_EXCEPTION_WHEN_CALLED("geworfene Exception bei Aufruf"), +} diff --git a/translations/en_GB/atrium-translations-en_GB-common/src/main/kotlin/ch/tutteli/atrium/translations/DescriptionFunLikeAssertion.kt b/translations/en_GB/atrium-translations-en_GB-common/src/main/kotlin/ch/tutteli/atrium/translations/DescriptionFunLikeAssertion.kt new file mode 100644 index 000000000..a65bdbdef --- /dev/null +++ b/translations/en_GB/atrium-translations-en_GB-common/src/main/kotlin/ch/tutteli/atrium/translations/DescriptionFunLikeAssertion.kt @@ -0,0 +1,13 @@ +package ch.tutteli.atrium.translations + +import ch.tutteli.atrium.assertions.DescriptiveAssertion +import ch.tutteli.atrium.reporting.translating.StringBasedTranslatable + +/** + * Contains the [DescriptiveAssertion.description]s of the assertion functions which are applicable to [Any]. + */ +enum class DescriptionFunLikeAssertion(override val value: String) : StringBasedTranslatable { + IS_NOT_THROWING_1("does not"), + IS_NOT_THROWING_2("throw when called"), + THROWN_EXCEPTION_WHEN_CALLED("thrown exception when called"), +}