github robstoll/atrium v0.9.0
Expect as replacement for Assert and jdk8/kotlin 1.3 specific assertions

latest releases: v1.3.0-alpha-1, v1.2.0, v1.2.0-RC1...
5 years ago

API Maturity: Stable
Implementation Maturity: Almost Stable

There won't be any breaking changes in the API (assertion functions/builders) until v1.0.0 besides parameter name renaming and experimental features. But we want to progress as well and deprecate functionality in each version (e.g quite a lot with 0.7.0; please replace deprecated functionality until v1.0.0 where we will remove it).
However, we do not provide yet a stable API for the domain and core modules of Atrium -- it is almost stable, but there might be slight breaking changes which we want to introduce before v1.0.0. That is also the reason why we do not have yet established backward compatibility tests for domain/core. This might affect you if you write your own assertion functions. And it also affects you if you provide your own implementation for parts of Atrium.

Table of Content

  • New Features
    • API
    • Domain/Core aka write own assertion functions
    • Others
  • Fixes
  • Improvments
  • Breaking Changes
  • Deprecation
  • Migrating deprecated functionality

New Features

new API fluent-en_GB

  • #26 Introduced Expect<T> as replacement for Assert<out T> => things like expect(1).toBe("not an int") result now in a compile error
  • #66 allow to pass single Char to starts(Not)With/ends(Not)With => thanks to @kssc0112 for the implementation
  • #128 Iterable.contains.inOrder.only.elementsOf => thanks to @npswedberg for the implementation
  • #129 Iterable.contains.inAnyOrder.elementsOf => thanks to @sanatik for the implementation
  • #127 Iterable.contains.inAnyOrder.only.elementsOf => thanks to @johnGachihi for the implementation
  • #158 Iterable.hasNext/hasNotNext => thanks to @sanatik for the implementation
  • #163 shortcut for Iterable.min() => thanks to @piyushmor for the implementation
  • #163 shortcut for Iterable.min() => thanks to @Megamiun for the implementation
  • #130 CharSequence.contains.elementsOf => thanks to @mattyway for the implementation
  • #165 CharSequence.matches => thanks to @mikemolenda for the implementation
  • #165 CharSequence.mismatches => thanks to @shardulsonar for the implementation
  • #183 Array.asList => thanks to @piraces for the implementaiton

jdk8 extensions

  • #108 Path.exists and Path.existsNot with excellent failure hints => big thanks to @jGleitz for the implementation
  • #111 shortcut for Paht.isDirectory and Paht.isRegularFile => thanks to @jGleitz for the implementation
  • #112 shortcut for Paht.isReadable and Paht.isWritable => thanks to @jGleitz for the implementation
  • #187 Path.startsWith => thanks to @arjank for the implementation
  • #189 Path.startsNotWith => thanks to @arjank for the implementation
  • #188 Path.endsWith => thanks to @gaconkzk for the implementation
  • #190 Path.endsNotWith => thanks to @segunfamisa for the implementation
  • #109 shortcut for Path.fileName => thanks to @Tregz for the implementation
  • #110 shortcut for Path.getParent => thanks to @lpicanco for the implementation
  • #169 shortcut for Path.extension => thanks to @lelloman for the implementation
  • #170 shortcut for Path.fileNameWithoutExtension => thanks to @aljacinto for the implementation
  • #166 File.asPath => thanks to @lelloman for the implementation
  • #261 LocalDate(Time) and ZonedDatetime.isBefore => thanks to @sandjelkovic for the start and @name213 for the finishing
  • #261 LocalDate(Time) and ZonedDatetime.isBeforeOrEqual => thanks to @name213 for the implementation
  • #175 LocalDate(Time) and ZonedDatetime.isAfter => thanks to @name213 for the implementation
  • #262 LocalDate(Time) and ZonedDatetime.isAfterOrEqual => thanks to @lukebiddell for the implementation
  • #290 LocalDate(Time) and ZonedDatetime.isEqual => thanks to @name213 for the implementation
  • #174 shortcut for LocalDate(Time) and ZonedDatetime.year => thanks to @lpicanco for the implementation
  • #175 shortcut for LocalDate(Time) and ZonedDatetime.month => thanks to @ShradhaSangtani for the implementation
  • #176 shortcut for LocalDate(Time) and ZonedDatetime.day => thanks to @ShradhaSangtani for the implementation
  • #175 shortcut for LocalDate(Time) and ZonedDatetime.dayOfWeek => thanks to @sanatik for the implementation
  • #47 Optional.isEmpty => thanks to @arjank for the implementation
  • #113 shortcut for Optional.get named isPresent => thanks to @slalu for the implementation

kotlin 1.3 extensions

Domain / Core

Features for assertion-function-writers:

  • introduced ExtractedFeaturePostStep and ChangedSubjectPostStep in order to have only one function on the domain level which covers both, narrowing features and features which expect an assertionCreator-lambda
  • ExpectImpl.feature.extractor
  • ExpectImpl.builder.representationOnly => assertion without description but only a representation

Others

  • #80 sample project for Atrium + junit5 => thanks to @bsemexan for the implementation
  • #207 sample project for Atrium + jasmine => thanks to @bsemexan for the implementation

Fixes

  • #143 domain-api-js was not included in bundle => thanks go to Darren Bell for reporting the issue

Improvements

  • #160 CharSequence.containsRegex accepts now also a Regex => thanks to @neelkamath for the implementation
  • #85 switch to implement instead of compile in build.gradle where possible => thanks to @kssc0112 for the implementation
    due to this cc-en_GB should no longer show up in cc-infix-en_GB
  • #193 update to spek 2.0.8 and use Spek's include => thanks to @Anubhav007 for the implementation
  • #202 use spek-js instead of dummy impl => thanks to @wudmer for the implementation
  • #239 spec for elementsOf empty iterable => thanks to @Megamiun for the implementation
  • #218 update tutteli-spek-extensions to 1.0.1 => thanks to @ShradhaSangtani for the implementation
  • #86 switch to native emoij => thanks to @Frandall
  • #156 improve specs for iterable, use one time consumable iterables => thanks to @aljacinto for the analysis and implementation
  • #296 git guide for newcomers as well as other fixes in CONTRIBUTING.md => thanks to @johnGachihi for the implementation
  • #285 do not report value type for map contains values
  • #297 rename ...OrEquals to ...OrEqual
  • #298 remove isA check in toBe for nullable types
  • #300 show only java's qualified name if different from kotlin => thanks to @Hubisco for the implementation
  • #303 workaround KT-35882, set cause of AtriumError explicit to null
  • #306 reword number of occurrences => thanks to @Jak-Sidious for the implementation
  • #307 change assertion verb in reporting => thanks to @Jak-Sidious for the implementation
  • #308 change toBe in reporting to equals => thanks to @Hubisco for the implementation

Breaking Changes

Planned (previously deprecated or announced)

  • none this time

Unplanned

  • none this time

Deprecation

The following was deprecated and will be removed with 1.0.0:

  • API cc-en_GB => use fluent-en_GB, infix-en_GB respectively
  • API cc-de_CH => there won't be a replacement, vote for #137 if you used it, we recommend you switch to fluent-en_GB
  • everything involving Assert/AssertionPlant and the like => switch to Expect and the like
  • Assert.subject, which means Expect.subject is deprecated as well. Subject is passed as argument to lambdas which have to deal with it.
    For instance, instead of writing createAndAddAssertion(TO_BE, expected) { subject == expected } one has to write createAndAddAssertion(TO_BE, expected) { it == expected }
  • ThrowableBuider including the assertion verb which dealt with exceptions (accepted a lambda) => can now be done with the regular expect function
  • SubjectProvider and AssertionHolder, both introduced in this version and it might well be we remove it with 1.0.0

The following deprecations are planned for a future version

  • this release does not yet include the new infix API. However, we are going to deprecate cc-infix-en_GB in favour of the new infix API infix-en_GB which is based on Expect as soon as the new infix API is ready.
  • AssertImpl will be deprecated in favour of ExpectImpl

Breaking Changes with 1.0.0

See atrium-roadmap -> Milestone 1.0.0

Migrating deprecated functionality

In case you migrate from a version < 0.7.0 then please have a look at the migration guide given in the Release Notes of 0.7.0 and 0.8.0.
Otherwise you can use the suggested replacements (ALT + Enter -> Replace with ...) or the search/replace patterns shown below.

Notice, that you don't have to migrate everything at once where asExpect and asAssert allow to switch between the old Assert and the new Expect world.
Ping us in the Atrium slack channel if you need help.

The following command is carrying out the points 1 to 11 described below (don't forget the points 12, 13, ...), run it from the root of your project, no guarantees that your system is capable of carrying it out. If not, you can use the manual steps described below

find ./ -path "*/test/*" -name "*.kt" | xargs perl -0777 -i \
-pe 's/AssertImpl([\n\r\s]*)\.changeSubject\(([^\)\n]+)\)[\n\r\s]*\{[\n\r\s]*subject/ExpectImpl$1.changeSubject\($2\)$1.unreported \{ it/g;' \
-pe 's/AssertImpl([\n\r\s]*)\.changeSubject\(([^\)]+)\)/ExpectImpl$1.changeSubject\($2\).unreported/g;' \
-pe 's/AssertImpl([\n\r\s]*)\.builder([\n\r\s]*)\.createDescriptive\(([^,\n]+,[^\)]+\)[\n\r\s]*\{[\n\r\s]*)plant.subject/AssertImpl([\n\r\s]*)\.builder([\n\r\s]*)\.createDescriptive\(([^,\n]+,[^\)]+\)[\n\r\s]*\{[\n\r\s]*)plant.subject/g;' \
-pe 's/AssertImpl([\n\r\s]*)\.builder([\n\r\s]*)\.descriptive([\n\r\s]*).withTest(\(?[\n\r\s]*)\{([\n\r\s]*)plant.subject/AssertImpl$1.builder$2.descriptive$3.withTest\(plant\)$4\{$5it/g;' \
-pe 's/AssertImpl([\n\r\s]*)\.builder([\n\r\s]*)\.descriptive([\n\r\s]*).withTest(\(?[\n\r\s]*)\{([\n\r\s]*)subject/AssertImpl$1.builder$2.descriptive$3.withTest\(this\)$4\{$5it/g;' \
-pe 's/(?:property|returnValueOf|rueckgabewertVon)\((?:[\n\r\s]*)subject::([^\)]+)\)(\s*\{)/feature(\{ f(it::$1) \})$2/g;' \
-pe 's/(?:property|returnValueOf|rueckgabewertVon)\((?:[\n\r\s]*)subject::([^\)]+)\)/feature \{ f(it::$1) \}/g;' \
-pe 's/(?:property|returnValueOf|rueckgabewertVon)\((?:[\n\r\s]*)subject::([^\)]+)\)(\s*\{)`/feature(\{ f(it::$1) \})$2/g;' \
-pe 's/(?:property|returnValueOf|rueckgabewertVon)\(([A-Z][^:]+)::([^\)]+)\)/feature($1::$2)/g;' \
-pe 's/(?:property|returnValueOf|rueckgabewertVon)\(([^:]+)::([^\)]+)\)/feature("$1.$2", { $1.$2 })/g;' \
-pe 's/(import ch\.tutteli\.atrium\.api\.cc\.(?:\.infix)?(?:en_GB|de_CH))\.(property|returnValueOf)/$1.feature/g;' \
-pe 's/(\.| )((?:toThrow|wirft|isA|istEin)<.*>)\s*\{\s*\}/$1$2()/g;' \
-pe 's/notToBeNull\s*\{\s*\}/notToBe(null)/g;' \
-pe 's/fun <T\s*:\s*Any> ([^\(]+)\(subject:\s*T\)[\n\r\s]*=[\n\r\s]*AssertImpl[\n\r\s]*\.coreFactory[\n\r\s]*\.newReportingPlant\(([^,]+),[\n\r\s]*\{\s*subject\s*\}[\n\r\s]*,[\n\r\s]*reporter\)/fun <T> $1\(subject: T\): Expect<T> = \n    ExpectBuilder.forSubject\(subject\)\n        .withVerb\($2\)\n        .withoutOptions\(\)\n        .build\(\)/g;' \
-pe 's/fun <T\s*:\s*Any> ([^\(]+)\(subject:\s*T\s*,[\n\r\s]*assertionCreator: Assert<T>.\(\)\s*->\s*Unit\)[\n\r\s]*=[\n\r\s]*AssertImpl[\n\r\s]*\.coreFactory[\n\r\s]*\.newReportingPlantAndAddAssertionsCreatedBy\(([^,]+),[\n\r\s]*\{\s*subject\s*\}[\n\r\s]*,[\n\r\s]*reporter,[\n\r\s]*assertionCreator\)/fun <T> $1\(subject: T, assertionCreator: Expect<T>.\(\) -> Unit\): Expect<T> = \n    $1(subject).addAssertionsCreatedBy(assertionCreator)/g;' \
-pe 's/(?:internal )?fun <T(?:\s*:\s*Any\?)?> ([^\(]+)\(subject:\s*T\)[\n\r\s]*=[\n\r\s]*AssertImpl[\n\r\s]*\.coreFactory[\n\r\s]*\.newReportingPlantNullable\(([^,]+),[\n\r\s]*\{\s*subject\s*\}[\n\r\s]*,[^\)]+\)//g;' \
-pe 's/import ch.tutteli.atrium.verbs\.(expect|assert|assertThat)/import ch.tutteli.atrium.api.verbs.$1/g;' \
-pe 's/AssertImpl/ExpectImpl/g;' \
-pe 's/fun Assert(?:ionPlant(?:Nullable)?)?<(.*)>\./fun <T: $1> Expect<T>\./g;' \
-pe 's/Assert(ionPlant(Nullable)?)?</Expect</g;' \
-pe 's/import ch\.tutteli\.atrium\.creating\.Assert(ionPlant(Nullable)?)?/import ch.tutteli.atrium.creating.Expect/g;' \
-pe 's/import ch.tutteli\.atrium\.api\.cc\.(en_GB|de_CH)/import ch.tutteli.atrium.api.fluent.$1/g;' \
-pe 's/is(Less|Greater)OrEquals/is$1ThanOrEqual/g;'

The following list helps you to migrate faster by using a few regex search replace commands (in Intellij). Make sure you have checked Regex as well as Match Case in the search options. Notice, that the code will certainly not compile after a single replace, you need to carry out all search&replace commands.
It is not perfect, maybe you need to do a few adjustments in addition, let us now and we improve the search/replace commands here.

  1. Switch to ExpectImpl.changeSubject instead of using AssertImpl.changeSubject:
    Search: AssertImpl([\n\r\s]*)\.changeSubject\(([^\)\n]+)\)[\n\r\s]*\{[\n\r\s]*subject
    Replace: ExpectImpl$1.changeSubject\($2\)$1.unreported { it

    Search: AssertImpl([\n\r\s]*)\.changeSubject\(([^\)]+)\)
    Replace: ExpectImpl$1.changeSubject\($2\).unreported

  2. builder.descriptive, safe withTest

    Search: AssertImpl([\n\r\s]*)\.builder([\n\r\s]*)\.createDescriptive\(([^,\n]+,[^\)]+\)[\n\r\s]*\{[\n\r\s]*)plant.subject
    Replace: AssertImpl$1.builder$2.createDescriptive\(plant, $3it

    Search: AssertImpl([\n\r\s]*)\.builder([\n\r\s]*)\.descriptive([\n\r\s]*).withTest(\(?[\n\r\s]*)\{([\n\r\s]*)plant.subject
    Replace: AssertImpl$1.builder$2.descriptive$3.withTest\(plant\)$4{$5it

    Search: AssertImpl([\n\r\s]*)\.builder([\n\r\s]*)\.descriptive([\n\r\s]*).withTest(\(?[\n\r\s]*)\{([\n\r\s]*)subject
    Replace: AssertImpl$1.builder$2.descriptive$3.withTest\(this\)$4{$5it

  3. use new feature mechanism

    This one needs extra care as arguments could be function calls. Verify the replacements

    Search: (?:property|returnValueOf|rueckgabewertVon)\((?:[\n\r\s]*)subject::([^\)]+)\)(\s*\{)
    Replace: feature(\{ f(it::$1) \})$2

    Search: (?:property|returnValueOf|rueckgabewertVon)\((?:[\n\r\s]*)subject::([^\)]+)\)
    Replace: feature \{ f(it::$1) \}

    Search: (?:property|returnValueOf|rueckgabewertVon)\(([A-Z][^:]+)::([^\)]+)\)
    Replace: feature($1::$2)

    Search: (?:property|returnValueOf|rueckgabewertVon)\(([^:]+)::([^\)]+)\)
    Replace: feature("$1.$2", { $1.$2 })

    Search: (import ch\.tutteli\.atrium\.api\.cc\.(?:\.infix)?(?:en_GB|de_CH))\.(property|returnValueOf)
    Replace: $1.feature

  4. toThrow and isA with empty assertionCreator lambda
    Search: (\.| )((?:toThrow|wirft|isA|istEin)<.*>)\s*\{\s*\}
    Replace $1$2()

  5. notToBeNull with empty assertionCreator lambda
    Search: notToBeNull\s*\{\s*\}
    Replace: notToBe(null)

  6. migrate custom assertion verbs:

    Search: fun <T\s*:\s*Any> ([^\(]+)\(subject:\s*T\)[\n\r\s]*=[\n\r\s]*AssertImpl[\n\r\s]*\.coreFactory[\n\r\s]*\.newReportingPlant\(([^,]+),[\n\r\s]*\{\s*subject\s*\}[\n\r\s]*,[\n\r\s]*reporter\)
    Replace:
    fun <T> $1\(subject: T\): Expect<T> = \n ExpectBuilder.forSubject\(subject\)\n .withVerb\($2\)\n .withoutOptions\(\)\n .build\(\)

    Search:
    fun <T\s*:\s*Any> ([^\(]+)\(subject:\s*T\s*,[\n\r\s]*assertionCreator: Assert<T>.\(\)\s*->\s*Unit\)[\n\r\s]*=[\n\r\s]*AssertImpl[\n\r\s]*\.coreFactory[\n\r\s]*\.newReportingPlantAndAddAssertionsCreatedBy\(([^,]+),[\n\r\s]*\{\s*subject\s*\}[\n\r\s]*,[\n\r\s]*reporter,[\n\r\s]*assertionCreator\)
    Replace:
    fun <T> $1\(subject: T, assertionCreator: Expect<T>.\(\) -> Unit\): Expect<T> = \n $1(subject).addAssertionsCreatedBy(assertionCreator)

    Search:
    (?:internal )?fun <T(?:\s*:\s*Any\?)?> ([^\(]+)\(subject:\s*T\)[\n\r\s]*=[\n\r\s]*AssertImpl[\n\r\s]*\.coreFactory[\n\r\s]*\.newReportingPlantNullable\(([^,]+),[\n\r\s]*\{\s*subject\s*\}[\n\r\s]*,[^\)]+\)
    Replace: (empty string)

    In case the above search&replace did not find anything (because your code is different):
    Switch from AssertImpl.coreFactory.newReportingPlant to ExpectBuilder
    => see atriumVerbs.kt for an example of how own assertion verbs are defined now; or use the suggested replacements but please add import ch.tutteli.atrium.domain.builders.reporting.ExpectBuilder first as it will not work correctly otherwise due to an Intellij bug
    => Note that you don't need a verb for nullable types any more. Thus:

    • remove the upper bound T: Any
    • remove the verb which uses `newReportingPlantNullable
    • remove the verb which expected act: () -> Unit
  7. Switch to new built-in assertion verbs which use Expect

    Search: import ch.tutteli.atrium.verbs.(expect|assert|assertThat)
    Replace: import ch.tutteli.atrium.api.verbs.$1

  8. Switch from AssertImpl to ExpectImpl

    Search: AssertImpl
    Replace: ExpectImpl

  9. Switch all your assertion functions to use Expect and no longer Assert:

    Search: import ch\.tutteli\.atrium\.creating\.Assert(ionPlant(Nullable)?)?
    Replace: import ch.tutteli.atrium.creating.Expect

    Search: fun Assert(?:ionPlant(?:Nullable)?)?<(.*)>\.
    Replace: fun <T: $1> Expect<T>\.

    Search: Assert(ionPlant(Nullable)?)?<
    Replace: Expect<

  10. Switch the API

    Search: import ch.tutteli\.atrium\.api\.cc\.(en_GB|de_CH)
    Replace: import ch.tutteli.atrium.api.fluent.$1

  11. isLessOr/isGreaterOrEquals

    Search: is(Less|Greater)OrEquals
    Replace: is$1ThanOrEqual

  12. In case you have custom assertion verbs
    Dealing with thrown exceptions is now handled by Expect as well.
    However, in case you have named the assertion verb differently for expecting an Exception then you have to decide:

  • use the same name => rename the corresponding function which expects act: () -> Unit to the same name and remove it afterwards
  • use a different name => delegate the function which expects act: () -> Unit to the other verb

Check if you need to add import ch.tutteli.atrium.domain.builders.reporting.ExpectBuilder

  1. Try to reduce duplicated Expect imports
    Repeat until you don't have duplicate imports anymore
    Search: import ch\.tutteli\.atrium\.creating\.Expect\n\s*import ch\.tutteli\.atrium\.creating\.Expect
    Replace: import ch.tutteli.atrium.creating.Expect

  2. Try to compile your project and watch out for the following warnings:

  • 'MyClass' is a final type, and thus a value of the type parameter is predetermined
    => you can suppress this warning by adding @file:Suppress("FINAL_UPPER_BOUND") to your file, this is actually a Kotlin bug (https://youtrack.jetbrains.com/issue/KT-34257)

Don't miss a new atrium release

NewReleases is sending notifications on new releases.