A multiplatform assertion library for Kotlin

Overview

Download EUPL atrium @ kotlinlang.slack.com Build Status Ubuntu Build Status Windows Coverage Newcomers Welcome

Atrium

Atrium is an open-source multiplatform assertion library for Kotlin with support for JVM, JS and Android. It is designed to support multiple APIs, different error reporting styles and Internationalization (i18n). The project was inspired by AssertJ at first but moved on and provides now more flexibility, features and hints to its users (so to you 😉 ).

Atrium is designed to be extensible as well as configurable and allows you to extend it with your own assertion functions, customise reporting or even replace core components with your own implementation in an easy way.

Atrium currently provides two API Styles: pure fluent and infix where both of them have their design focus on usability in conjunction with code completion functionality provided by your IDE. See Examples below to get a feel for how you could benefit from Atrium.


You are taking a sneak peek at the next version. Please have a look at the README of the git tag in case you are looking for the documentation of the corresponding version. For instance, the README of v0.16.0.


Table of Content

Installation

JVM

Atrium is linked to mavenCentral, jcenter but can also be retrieved directly from bintray.

gradle:

buildscript {
    ext { atrium_version='0.16.0' }
}
repositories {
    mavenCentral()
}
dependencies {
    testImplementation "ch.tutteli.atrium:atrium-fluent-en_GB:$atrium_version"
}

We have defined a dependency to the bundle atrium-fluent-en_GB in the above example which provides a pure fluent API (in en_GB) for the JVM platform.

Have a look at the JVM sample projects for a quick setup, or Maven sample project if you prefer Maven to Gradle.

We currently provide the following extensions for the JVM platform:

  • kotlin_1_3: assertion functions for Kotlin 1.3 specific types (e.g. for Result).

You can enable them as follows:

dependencies {
    testImplementation "ch.tutteli.atrium:atrium-api-fluent-en_GB-kotlin_1_3:$atrium_version"
}

Also take a look at Third-party Extensions which might come in handy as well.

click to see how the setup for the infix API looks like
buildscript {
    ext { atrium_version='0.16.0' }
}
repositories {
    mavenCentral()
}
dependencies {
    testImplementation "ch.tutteli.atrium:atrium-infix-en_GB:$atrium_version"
}

And for the aforementioned extensions:

dependencies {
    testImplementation "ch.tutteli.atrium:atrium-api-infix-en_GB-kotlin_1_3:$atrium_version"
}


maven:
Because maven is a bit more verbose than gradle, the example is not listed here but a sample maven project is provided which shows all necessary setup.

That is all, you are all set. Jump to Examples which shows how to use Atrium.

JS

buildscript {
    ext { atrium_version='0.16.0' }
}
repositories {
    mavenCentral()
}
dependencies {
    testImplementation("ch.tutteli.atrium:atrium-fluent-en_GB-js:$atrium_version")
}

We have defined a dependency to the bundle atrium-fluent-en_GB-js in the above example which provides a pure fluent API (in en_GB) for the JS platform.

Have a look at the JS sample projects for a quick setup.

Otherwise, you need to setup an explicit dependency on atrium-fluent-en_GB-js in your test code in order that you can use Atrium. This is due to the loosely coupled design of Atrium and dead code elimination performed by the Kotlin compiler for JS.

Atrium itself is using mocha as well (see build.gradle -> createJsTestTask) and has tests written in JS modules (see AdjustStackTest) as well as tests written in common modules (e.g. SmokeTest) which are executed on the JS platform as well (actually on all platforms -> JVM uses JUnit for this purpose, see build.gradle -> useJupiter).

We currently provide the following extensions for the JS platform:

  • kotlin_1_3: assertion functions for Kotlin 1.3 specific types (e.g. for Result).

You can enable them as follows:

dependencies {
    testImplementation "ch.tutteli.atrium:atrium-api-fluent-en_GB-kotlin_1_3-js:$atrium_version"
}
click to see how the setup for the infix API looks like
buildscript {
    ext { atrium_version='0.16.0' }
}
repositories {
    mavenCentral()
}
dependencies {
    testImplementation "ch.tutteli.atrium:atrium-infix-en_GB-js:$atrium_version"
}

and for the aforementioned extensions:

dependencies {
    testImplementation "ch.tutteli.atrium:atrium-api-infix-en_GB-kotlin_1_3-js:$atrium_version"
}

That is all, you are all set. Jump to Examples which shows how to use Atrium.

Android

Starting with 0.12.0 we no longer deliver a dedicated -android jar. Instead you can use the same setup as shown in JVM setup. We start adding one again in case we have Android specific assertion functions.

Also take a look at Third-party Extensions which might come in handy as well.

Common

The setup for using Atrium in a common module of a multiplatform project is basically the same as for the JVM setup, you only need to suffix the dependency with -common in addition. For instance atrium-fluent-en_GB-common instead of atrium-fluent-en_GB.

Have a look at JVM, JS or Android to see how the setup of a specific platform has to be done. You might want to have a look at the Multiplatform sample project as well for a quick setup.

Examples

We are using the API provided by the bundle module atrium-fluent-en_GB in the following examples. It provides a pure fluent API for the JVM platform. Have a look at apis/differences.md to see how the infix API looks like, how they differ respectively.

Your First Assertion

We start off with a simple example:

import ch.tutteli.atrium.api.fluent.en_GB.*
import ch.tutteli.atrium.api.verbs.expect

val x = 10
expect(x).toEqual(9)

ExampleOutput

expected that subject: 10        (kotlin.Int <1234789>)
◆ equals: 9        (kotlin.Int <1234789>)

The statement can be read as "I expect, x to be nine" where an equality check is used (for an identity check, you have to use toBeTheSameInstace). Since this is false, an AssertionError is thrown with a corresponding message as shown in the Output where ◆ ... represents a single assertion for the subject (10 in the above example) of the assertion. In this sense the report can be read as I expected that the subject of the assertion, which is 10, equals 9 -- and needless to say, this assertion is wrong and thus the thrown error.

We are using the bundle atrium-fluent-en_GB and the predefined expectation verb expect in the examples. Thus, the corresponding imports at the beginning of the file in the above example. We will omit the import statements in the remaining examples for brevity.

You want to run the examples yourself? Have a look at the Installation section which explains how to set up a dependency to Atrium.

The next section shows how you can define multiple assertions for the same subject.

Define Single Assertions or Assertion Groups

// two single assertions, only first evaluated
expect(4 + 6).toBeLessThan(5).toBeGreaterThan(10)

ExampleOutput

expected that subject: 10        (kotlin.Int <1234789>)
◆ is less than: 5        (kotlin.Int <1234789>)

Atrium allows you to chain assertions or in other words you only need to write the expect(...) part once and can make several single assertions for the same subject. The expression which determines the subject of the assertion (4 + 6 in the above example) is evaluated only once.

In this sense we could have written it also as follows (which is only the same because 4 + 6 does not have side effects).

expect(4 + 6).toBeLessThan(5)
expect(4 + 6).toBeGreaterThan(10)

Correspondingly, the first expect statement (which does not hold) throws an AssertionError. In the above example, toBeLessThan(5) is already wrong and thus toBeGreaterThan(10) was not evaluated at all and correspondingly not reported.

If you want that both assertions are evaluated together, then use the assertion group syntax as follows:

// assertion group with two assertions, both evaluated
expect(4 + 6) {
    toBeLessThan(5)
    toBeGreaterThan(10)
}

ExampleOutput

expected that subject: 10        (kotlin.Int <1234789>)
◆ is less than: 5        (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. The reporting can be read as I expected that the subject of the assertion, which is 10, is less than 5 and is greater than 10

You can use and as filling element between single assertions and assertion group blocks:

expect(5).toBeGreaterThan(2).and.toBeLessThan(10)

expect(5) {
    // ...
} and { // if the previous block fails, then this one is not evaluated
    // ...
}

Expect an Exception

expect {
    // this block does something but eventually...
    throw IllegalArgumentException("name is empty")
}.toThrow<IllegalStateException>()

ExampleOutput

expected that subject: () -> kotlin.Nothing        (readme.examples.MostExamplesSpec$1$7$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.MostExamplesSpec$1$7$1.invoke(MostExamplesSpec.kt:67)
        ⚬ readme.examples.MostExamplesSpec$1$7$1.invoke(MostExamplesSpec.kt:22)
        ⚬ readme.examples.MostExamplesSpec$1$7.invoke(MostExamplesSpec.kt:272)
        ⚬ readme.examples.MostExamplesSpec$1$7.invoke(MostExamplesSpec.kt:22)

You can also pass a lambda to expect and then use toThrow to make the assertion that invoking the lambda throws a certain exception (IllegalStateException in the example above).

As with all narrowing functions, there are two overloads:

  • the first is parameterless and turns only the subject into the expected type; failing to do so cannot include additional information in error reporting though.
  • the second expects an assertionCreator-lambda in which you can define sub-assertions. An assertionCreator-lambda has always the semantic of an assertion group block. It has also the benefit, that Atrium can provide those sub-assertions in error reporting, showing some additional context in case of a failure.

The following example uses the first overload

expect {
    throw IllegalArgumentException()
}.toThrow<IllegalArgumentException>().message.toStartWith("firstName")

ExampleOutput

expected that subject: () -> kotlin.Nothing        (readme.examples.MostExamplesSpec$1$8$1 <1234789>)
◆ ▶ thrown exception when called: java.lang.IllegalArgumentException
    ◾ ▶ message: null
        ◾ is instance of type: String (kotlin.String) -- Class: java.lang.String

And this one uses the second overload; notice the difference in reporting.

expect {
    throw IllegalArgumentException()
}.toThrow<IllegalArgumentException> {
    message { toStartWith("firstName") }
}

ExampleOutput

expected that subject: () -> kotlin.Nothing        (readme.examples.MostExamplesSpec$1$9$1 <1234789>)
◆ ▶ thrown exception when called: java.lang.IllegalArgumentException
    ◾ ▶ message: null
        ◾ is instance of type: String (kotlin.String) -- Class: java.lang.String
          » starts with: "firstName"        <1234789>

As side notice, message is a shortcut for feature(Throwable::message).notToBeNull, which creates a feature assertion (see next section) about Throwable::message.

There is also the counterpart to toThrow named notToThrow:

expect {
    // this block does something but eventually...
    throw IllegalArgumentException("name is empty", RuntimeException("a cause"))
}.notToThrow()

ExampleOutput

expected that subject: () -> kotlin.Nothing        (readme.examples.MostExamplesSpec$1$10$1 <1234789>)
◆ ▶ invoke(): ❗❗ threw java.lang.IllegalArgumentException
    ℹ Properties of the unexpected IllegalArgumentException
      » message: "name is empty"        <1234789>
      » stacktrace: 
        ⚬ readme.examples.MostExamplesSpec$1$10$1.invoke(MostExamplesSpec.kt:88)
        ⚬ readme.examples.MostExamplesSpec$1$10$1.invoke(MostExamplesSpec.kt:22)
        ⚬ readme.examples.MostExamplesSpec$1$10.invoke(MostExamplesSpec.kt:89)
        ⚬ readme.examples.MostExamplesSpec$1$10.invoke(MostExamplesSpec.kt:22)
      » cause: java.lang.RuntimeException
          » message: "a cause"        <1234789>
          » stacktrace: 
            ⚬ readme.examples.MostExamplesSpec$1$10$1.invoke(MostExamplesSpec.kt:88)

Notice that stacks are filtered so that you only see what is of interest. Filtering can be configured via ReporterBuilder by choosing an appropriate AtriumErrorAdjuster. Stack frames of Atrium and of test runners (Spek, Kotlintest and JUnit for JVM, mocha and jasmine for JS) are excluded per default. Create a Feature Request in case you use a different runner, we can add yours to the list as well.

Feature Assertions

Many times you are only interested in certain features of the subject and want to make assertions about them.

There are different use cases for feature assertions. We will start of with properties and method calls and go on with more complicated scenarios.

Property and Methods

We are using the data class Person in the following examples:

data class Person(val firstName: String, val lastName: String, val isStudent: Boolean) {
    fun fullName() = "$firstName $lastName"
    fun nickname(includeLastName: Boolean) = when (includeLastName) {
        false -> "Mr. $firstName"
        true -> "$firstName aka. $lastName"
    }
}

val myPerson = Person("Robert", "Stoll", false)

The simplest way of defining assertions for a property of an instance or for the return value of a method call is by using the extension method its.

expect(myPerson)
    .its({ isStudent }) { toEqual(true) } // fails, subject still Person afterwards
    .its { fullName() }                   // not evaluated anymore, subject String afterwards
    .toStartWith("rob")                   // not evaluated anymore

ExampleOutput

expected that subject: Person(firstName=Robert, lastName=Stoll, isStudent=false)        (readme.examples.FeatureExtractorSpec$1$Person <1234789>)
◆ ▶ its.definedIn(FeatureExtractorSpec.kt:43): false
    ◾ equals: true

In the above example we created two assertions, one for the property isStudent of myPerson and a second one for the return value of calling fullName() on myPerson. A feature assertion is indicated as follows in reporting: It starts with a followed by the feature's description and its actual value. So the above output can be read as

I expected that the subject of the assertion, which is actually the Person(...), respectively its property which was defined in FeatureExtractorSpec.kt on line 43, which is actually false, equals true.

The second feature is not shown in reporting as the first already failed and we have chosen to use single assertions which have fail-fast semantic.

Feature assertions follow the common pattern of having two overloads:

  • the first expects only the extractor-lambda. This overload narrows the subject to the feature, meaning a subsequent call in the fluent chain is about the feature and not the previous subject.

  • the second expects an assertionCreator-lambda in addition, in which you can define sub-assertions for the feature. An assertionCreator-lambda has always the semantic of an assertion group block or in other words, not-fail fast. It has also the benefit, that Atrium can provide those sub-assertions in error reporting, Moreover, the subject stays the same so that subsequent calls are still about the same subject.

    expect(myPerson) { // forms an assertion group block
    
        its({ firstName }) {   // forms an assertion group block
            toStartWith("Pe")  // fails
            toEndWith("er")    // is evaluated nonetheless
        }                      // fails as a whole
    
        // still evaluated, as it is in outer assertion group block
        its { lastName }.toEqual("Dummy")
    }

    ExampleOutput

    expected that subject: Person(firstName=Robert, lastName=Stoll, isStudent=false)        (readme.examples.FeatureExtractorSpec$1$Person <1234789>)
    ◆ ▶ its.definedIn(FeatureExtractorSpec.kt:52): "Robert"        <1234789>
        ◾ starts with: "Pe"        <1234789>
        ◾ ends with: "er"        <1234789>
    ◆ ▶ its.definedIn(FeatureExtractorSpec.kt:58): "Stoll"        <1234789>
        ◾ equals: "Dummy"        <1234789>
    

One drawback of its (which we plan to improve but most likely not before we drop support for Kotlin < 1.5) is that reading the resulting feature description does not immediately tell us what feature we extracted.

That is where the feature function comes into play. It is based on reflection and uses the name of the feature as description. Following the first example rewritten to feature.

expect(myPerson)
    .feature({ f(it::isStudent) }) { toEqual(true) } // fails, subject still Person afterwards
    .feature { f(it::fullName) }                     // not evaluated anymore, subject String afterwards
    .toStartWith("rob")                              // not evaluated anymore

ExampleOutput

expected that subject: Person(firstName=Robert, lastName=Stoll, isStudent=false)        (readme.examples.FeatureExtractorSpec$1$Person <1234789>)
◆ ▶ isStudent: false
    ◾ equals: true

The report reads much nicer now:

I expected that the subject of the assertion, which is actually the Person(...), respectively its property isStudent, which is actually false, equals true

The drawback of feature compared to its is its syntax. Certainly, one has to get used to it first. Another is that you might run into Ambiguity Problems due to Kotlin bugs.

feature has several overloads, we are looking at the one expecting a lambda in which you have to provide a MetaFeature. Creating a MetaFeature is done via the function f by passing in a bounded reference of the corresponding property or method (including arguments if required). it within the MetaFeature-provider-lambda refers to the subject of the assertion (myPerson in the above example).

Also feature follows the common pattern of having two overloads where the second expects an assertionCreator-lambda. Following the second example rewritten from its to feature:

expect(myPerson) { // forms an assertion group block

    feature({ f(it::firstName) }) { // forms an assertion group block
        toStartWith("Pe")           // fails
        toEndWith("er")             // is evaluated nonetheless
    }                               // fails as a whole

    // still evaluated, as it is in outer assertion group block
    feature { f(it::lastName) }.toEqual("Dummy")
}

ExampleOutput

expected that subject: Person(firstName=Robert, lastName=Stoll, isStudent=false)        (readme.examples.FeatureExtractorSpec$1$Person <1234789>)
◆ ▶ firstName: "Robert"        <1234789>
    ◾ starts with: "Pe"        <1234789>
    ◾ ends with: "er"        <1234789>
◆ ▶ lastName: "Stoll"        <1234789>
    ◾ equals: "Dummy"        <1234789>

Atrium provides several shortcuts for commonly used properties so that you can use them instead of writing its { ... } / feature(...) all the time. For instance, message for Throwable (see Expect an Exception), first and second for Pair and others. Please open a feature request in case you miss a shortcut.

💬 <- this icon signifies answers/input for advanced users, you might want to skip them if you are new to Atrium.

💬 Wrap each property into an assertion function?

You might be asking yourself whether it is better to write an own assertion function or use feature.

The only drawback of using an existing property is that a few more key strokes are required compared to writing an own assertion function once and then reuse it (as we did with message). Yet, we do not recommend to write an own assertion function for every single property. We think it makes sense to add one if you use it a lot and (preferably) it is a stable API. Why not always? Because one quickly forgets to rename the assertion function if the property as such is renamed (e.g., as part of an IDE refactoring). As you can see, you would need to keep the property name and the name of the assertion function in sync to be meaningful (otherwise one gets quickly confused or has to remember two names for the same thing).

Writing assertion functions for methods is a different story though, especially due to overload bugs in Kotlin. Also, code completion is not yet as good as it should be when it comes to methods. Last but not least, in case it is not always safe to call a method (e.g. List.get => IndexOutOfBound) then it makes sense to wrap it into an assertion function and use _logic.extractFeature instead.

Last but not least, let us have a look at an example where a method with arguments is used as feature:

expect(myPerson)
    .feature { f(it::nickname, false) } // subject narrowed to String
    .toEqual("Robert aka. Stoll")       // fails
    .toStartWith("llotS")               // not evaluated anymore

ExampleOutput

expected that subject: Person(firstName=Robert, lastName=Stoll, isStudent=false)        (readme.examples.FeatureExtractorSpec$1$Person <1234789>)
◆ ▶ nickname(false): "Mr. Robert"        <1234789>
    ◾ equals: "Robert aka. Stoll"        <1234789>

f supports methods with up to 5 arguments.

Atrium provides shortcuts for commonly used methods, e.g. List.get, Map.getExisting, Optional.toBePresent or Result.toBeSuccess where all of them include some additional checking (index bound, existence of the key within the map etc.) Please open a feature request in case you miss a shortcut.

💬 Write own feature assertion functions with additional checks.

Atrium provides a feature extractor which allows making feature assertions in a safe way in case the extraction is only valid for certain subjects. It is inter alia used for List.get

Arbitrary Features

A feature does not necessarily have to be directly related to the subject as properties or method calls do. Either use its the overload of feature which expects a feature description in form of a String as first argument. Following an example using feature.

data class FamilyMember(val name: String)

data class Family(val members: List<FamilyMember>)

val myFamily = Family(listOf(FamilyMember("Robert")))
expect(myFamily)
    .feature("number of members", { members.size }) { toEqual(1) } // subject still Family afterwards
    .feature("first member's name") { members.first().name }       // subject narrowed to String
    .toEqual("Peter")

ExampleOutput

expected that subject: Family(members=[FamilyMember(name=Robert)])        (readme.examples.FeatureExtractorSpec$1$Family <1234789>)
◆ ▶ first member's name: "Robert"        <1234789>
    ◾ equals: "Peter"        <1234789>

Also this version of feature provides two different kind of overloads:

  • the first expects a feature description and a feature-provider-lambda This overload narrows the subject to the feature, meaning a subsequent call in the fluent chain is about the feature and not the previous subject.

  • the second expects an assertionCreator-lambda in addition, in which you can define sub-assertions for the feature. An assertionCreator-lambda has always the semantic of an assertion group block or in other words, not-fail fast. It has also the benefit, that Atrium can provide those sub-assertions in error reporting, Moreover, the subject stays the same so that subsequent calls are still about the same subject.

As you can see, Atrium provides a generic way to postulate assertions about features. Yet, if you use such feature assertion often or it gets more complicated, then it might be worth to write an own assertion function where we recommend to use feature over its.

Within Assertion Functions

In case you write an own assertion function, then we discourage two things:

  • using its because the reporting reads less nice and it is also less efficient than feature
  • using feature with a MetaFeature-provider-lambda (as shown in Property and Methods)

Instead, we encourage you to pass a class references to feature. This has the benefit, that we can always show the feature name, also in case a previous feature extraction or subject transformation failed. Following an example:

fun <F : Any, T : Pair<F, *>> Expect<T>.firstToBeDoneWrong(expected: F) =
    feature({ f(it::first) }) { toEqual(expected) }

fun <F : Any, T : Pair<F, *>> Expect<T>.firstToBe(expected: F) =
    feature(Pair<F, *>::first) { toEqual(expected) }

expect(listOf(1 to "a", 2 to "b")).get(10) {
    firstToBeDoneWrong(1)
    firstToBe(1)
}

ExampleOutput

expected that subject: [(1, a), (2, b)]        (java.util.Arrays.ArrayList <1234789>)
◆ ▶ get(10): ❗❗ index out of bounds
      » ▶ CANNOT show description as it is based on subject which is not defined: 
          ◾ equals: 1        (kotlin.Int <1234789>)
      » ▶ first: 
          ◾ equals: 1        (kotlin.Int <1234789>)

Also, this version of feature provides to kind of overloads, one without and one with assertionCreator-lambda. (see for instance Arbitrary Features for more information).

Ambiguity Problems

Unfortunately there are several Kotlin bugs when it comes to overloading, especially in conjunction with KFunction (see Kotlin Bugs and upvote in case you run into one). It might happen that you run into such issues using feature in conjuction with a MetaFeature-provider-lambda (as shown in Property and Methods). However, Atrium provides alternative functions next to f within the MetaFeature-provider-lambda to disambiguate the situation. Use p for properties and f0 to f5 for methods. Likely you need to specify the type parameters manually as Kotlin is not able to infer them correctly.

class WorstCase {
    val propAndFun: Int = 1
    fun propAndFun(): Int = 1

    fun overloaded(): Int = 1
    fun overloaded(b: Boolean): Int = 1
}

expect(WorstCase()) {
    feature { p<Int>(it::propAndFun) }
    feature { f0<Int>(it::propAndFun) }
    feature { f0<Int>(it::overloaded) }
    feature { f1<Boolean, Int>(it::overloaded, true) }.toEqual(1)
}

Notice, that you might run into the situation that Intellij is happy but the compiler is not. For instance, Intellij will suggest that you can remove the type parameters in the above example. Yet, if you do so, then the compiler will fail, mentioning ambiguous overloads. Most of the time this problem stems from the reason that Intellij is using a newer Kotlin version to analyse than the one you compile your project with.

Next to using the alternative functions, you could also use its or the overload of feauture which expects a String as description (as shown in arbitrary features.

Property does not exist

In case you deal with Java code and are using feature, then you might run into the problem that a property does not exist. This is due to the fact that Kotlin only provides syntactic sugar to access a getter via property syntax. In such a case, use the get... method instead. For instance:

// java
class A { 
    public String getFoo() { return "bar"; } 
}
// kotlin
val a = A()
a.foo // syntactic sugar, accesses getFoo via property
expect(a)
    // feature{ f(it::foo) }    // would result in a compile error
    .feature { f(it::getFoo) }  // works
    .startsWith(...)

Type Assertions

interface SuperType

data class SubType1(val number: Int) : SuperType
data class SubType2(val word: String, val flag: Boolean) : SuperType

val x: SuperType = SubType2("hello", flag = true)
expect(x).toBeAnInstanceOf<SubType1>()
    .feature { f(it::number) }
    .toEqual(2)

ExampleOutput

expected that subject: SubType2(word=hello, flag=true)        (readme.examples.SubType2 <1234789>)
◆ is instance of type: SubType1 (readme.examples.SubType1)

You can narrow a type with the toBeA function. On one hand it checks that the subject of the current assertion (x in the above example) is actually the expected type and on the other hand it turns the subject into this type. This way you can make specific assertions which are only possible for the corresponding type -- for instance, considering the above example, number is not available on SuperType but only on SubType1.

expect(x).toBeAnInstanceOf<SubType2> {
    feature { f(it::word) }.toEqual("goodbye")
    feature { f(it::flag) }.toEqual(false)
}

ExampleOutput

expected that subject: SubType2(word=hello, flag=true)        (readme.examples.SubType2 <1234789>)
◆ ▶ word: "hello"        <1234789>
    ◾ equals: "goodbye"        <1234789>
◆ ▶ flag: true
    ◾ equals: false

There are two toBeA overloads:

  • the first (shown in the first example) is parameterless and turns only the subject into the expected type; failing to do so cannot include additional information in error reporting though.
  • the second (shown in the second example) expects an assertionCreator-lambda in which you can define sub-assertions. An assertionCreator-lambda has always the semantic of an assertion group block -- as a recapitulation, assertions in an assertion group block are all evaluated and failures are reported at the end of the block. It has also the benefit, that Atrium can provide those sub-assertions in error reporting, showing some additional context in case of a failure.

Nullable Types

Let us look at the case where the subject of the assertion has a nullable type.

val slogan1: String? = "postulating assertions made easy"
expect(slogan1).toEqual(null)

ExampleOutput

expected that subject: "postulating assertions made easy"        <1234789>
◆ equals: null
val slogan2: String? = null
expect(slogan2).toEqual("postulating assertions made easy")

ExampleOutput

expected that subject: null
◆ equals: "postulating assertions made easy"        <1234789>

On one hand, you can use toEqual and pass the same type -- String? in the above example, so in other words either null as in the first example or a String as in the second example. On the other hand, you can use notToEqualNull to turn the subject into its non-null version. This is a shortcut for toBeA<Xy> where Xy is the non-nullable type (see Type Assertions). Following an example:

expect(slogan2)        // subject has type String?
    .notToEqualNull()  // subject is narrowed to String
    .toStartWith("atrium")

ExampleOutput

expected that subject: null
◆ is instance of type: String (kotlin.String) -- Class: java.lang.String

Since notToEqualNull delegates to toBeA it also provides two overloads, one without (example above) and one with assertionCreator-lambda (example below); see Type Assertions for more information on the difference of the overloads.

expect(slogan2).notToEqualNull { toStartWith("atrium") }

ExampleOutput

expected that subject: null
◆ is instance of type: String (kotlin.String) -- Class: java.lang.String
  » starts with: "atrium"        <1234789>

Atrium provides one additional function which is intended for data driven testing involving nullable types and is explained in the corresponding section.

👓 <- this icon signifies additional information, worth reading in our opinion but if you are only after code examples, then you can skip now to the next section (otherwise click on the arrow to expand the section).

👓 dealing a lot with nullable types from Java...

... in this case we recommend having a look at the Java Interoperability section.

Collection Assertions

Atrium provides assertion builders which allow to make sophisticated toContain assertions for Iterable<T>. Such a building process allows you to define very specific assertions, where the process is guided by a fluent builder pattern. You can either use such an Assertion Builder to create a specific assertion or use one of the Shortcut Functions in case you have kind of a common case. The following sub sections show both use cases by examples.

Shortcut Functions

expect(listOf(1, 2, 2, 4)).toContain(2, 3)

ExampleOutput

expected that subject: [1, 2, 2, 4]        (java.util.Arrays.ArrayList <1234789>)
◆ contains, in any order: 
  ⚬ an element which equals: 3        (kotlin.Int <1234789>)
      » but no such element was found

The assertion function toContain(2, 3) is a shortcut for using a Sophisticated Assertion Builder -- it actually calls toContain.inAnyOrder.atLeast(1).values(2, 3). This is reflected in the output, which tells us that we expected that the number of such entries, which is actually 0, is at least: 1.

👓 and what about expected value 2?

Exactly, what about the expected value 2, why do we not see anything about it in the output? The output does not show anything about the expected value 2 because the default reporter reports only failing assertions.

Back to the shortcut functions.


Next to expecting that certain values are contained in or rather returned by an Iterable, Atrium allows us to use an assertionCreator-lambda to identify an element (an assertionCreator-lambda can also be thought of as matcher / predicate in this context). An element is considered as identified, if it holds all specified assertions. Following an example:

expect(listOf(1, 2, 2, 4)).toContain(
    { toBeLessThan(0) },
    { toBeGreaterThan(2).toBeLessThan(4) }
)

ExampleOutput

expected that subject: [1, 2, 2, 4]        (java.util.Arrays.ArrayList <1234789>)
◆ contains, in any order: 
  ⚬ an element which: 
      » is less than: 0        (kotlin.Int <1234789>)
      » but no such element was found
  ⚬ an element which: 
      » is greater than: 2        (kotlin.Int <1234789>)
      » is less than: 4        (kotlin.Int <1234789>)
      » but no such element was found

In the above example, neither of the two lambdas matched any elements and thus both are reported as failing (sub) assertions.

Another toContain shortcut function which Atrium provides for Iterable<T> is kind of the opposite of inAnyOrder.atLeast(1) and is named toContainExactly. Again, Atrium provides two overloads for it, one for values, e.g. toContainExactly(1, 2) which calls toContain.inOrder.only.values(1, 2) and a second one which expects one or more assertionCreator-lambda, e.g. toContainExactly( { toBeGreaterThan(5) }, { toBeLessThan(10) }) which calls toContain.inOrder.only.elements({ toBeGreaterThan(5) }, { toBeLessThan(10) }). We will spare the examples here and show them in the following sections. Notice that you can pass null to toContainExactly instead of an assertionCreator-lambda to match null. This makes of course only sense if your Iterable contains nullable elements.

Atrium provides also a notToContain shortcut function. Furthermore, it provides aliases for toContain and notToContain named toHaveNextAndAny and toHaveNextAndNone,
which might be a better choice if you think in terms of: expect a predicate holds. These two are completed with an toHaveNextAndAll assertion function.

Following each in action:

expect(listOf(1, 2, 3, 4)).toHaveElementsAndAny {
    toBeLessThan(0)
}

ExampleOutput

expected that subject: [1, 2, 3, 4]        (java.util.Arrays.ArrayList <1234789>)
◆ contains, in any order: 
  ⚬ an element which: 
      » is less than: 0        (kotlin.Int <1234789>)
      » but no such element was found

expect(listOf(1, 2, 3, 4)).toHaveElementsAndNone {
    toBeGreaterThan(2)
}

ExampleOutput

expected that subject: [1, 2, 3, 4]        (java.util.Arrays.ArrayList <1234789>)
◆ does not contain: 
  ⚬ an element which: 
      » is greater than: 2        (kotlin.Int <1234789>)
      ❗❗ following elements were mismatched: 
         ⚬ index 2: 3        (kotlin.Int <1234789>)
         ⚬ index 3: 4        (kotlin.Int <1234789>)

expect(listOf(1, 2, 3, 4)).toHaveElementsAndAll {
    toBeGreaterThan(2)
}

ExampleOutput

expected that subject: [1, 2, 3, 4]        (java.util.Arrays.ArrayList <1234789>)
◆ all entries: 
    » is greater than: 2        (kotlin.Int <1234789>)
    ❗❗ following elements were mismatched: 
       ⚬ index 0: 1        (kotlin.Int <1234789>)
       ⚬ index 1: 2        (kotlin.Int <1234789>)

Sophisticated Assertion Builders

Sophisticated assertion builders implement a fluent builder pattern. To use the assertion builder for sophisticated Iterable<T>-toContain-assertions, you can type toContain -- as you would when using the Shortcut Functions toContain -- but type . as next step (so that you are using the property toContain instead of one of the shortcut functions). Currently, the builder provides two options, either inAnyOrder or inOrder. In case you are using an IDE, you do not really have to think too much -- use code completion; the fluent builders will guide you through your decision-making 😊

Following on the last section we will start with an inOrder example:

expect(listOf(1, 2, 2, 4)).toContain.inOrder.only.entries({ toBeLessThan(3) }, { toBeLessThan(2) })

ExampleOutput

expected that subject: [1, 2, 2, 4]        (java.util.Arrays.ArrayList <1234789>)
◆ ▶ size: 4        (kotlin.Int <1234789>)
    ◾ equals: 2        (kotlin.Int <1234789>)
◆ contains only, in order: 
  ✔ ▶ element 0: 1        (kotlin.Int <1234789>)
      ◾ is less than: 3        (kotlin.Int <1234789>)
  ✘ ▶ element 1: 2        (kotlin.Int <1234789>)
      ◾ is less than: 2        (kotlin.Int <1234789>)
    ❗❗ additional elements detected: 
       ⚬ element 2: 2        (kotlin.Int <1234789>)
       ⚬ element 3: 4        (kotlin.Int <1234789>)

Since we have chosen the only option, Atrium shows us a summary where we see three things:

  • Whether a specified assertionCreator-lambda matched (signified by or ) the corresponding element or not (e.g. ✘ ▶ entry 1: was 2 and we expected, it is less than 2)
  • Whether the expected size was correct or not (✘ ▶ size: was 4, we expected it, to be: 2 -- see also Property Assertions)
  • and last but not least, mismatches or additional elements as further clue (❗❗ additional elements detected).

😍 We are pretty sure you are going to love this feature as well. Please star Atrium if you like using it.

💬 too verbose?

As side notice, in case you are dealing with large Iterable and do not want such a verbose output, then let use know by up-voting https://github.com/robstoll/atrium/issues/292 So far the verbose output was always handy for us, but you might have other test cases than we have. Also notice, that Atrium will not deal with infinite Iterables, i.e. you need to use take(100) or the like first.


Following one more example for inOrder as well as a few examples for inAnyOrder. We think explanations are no longer required at this stage. In case you have a question (no matter about which section), then please turn up in the atrium Slack channel (Invite yourself in case you do not have an account yet) and we happily answer your question there.

expect(listOf(1, 2, 2, 4)).toContain.inOrder.only.values(1, 2, 2, 3, 4)

ExampleOutput

expected that subject: [1, 2, 2, 4]        (java.util.Arrays.ArrayList <1234789>)
◆ ▶ size: 4        (kotlin.Int <1234789>)
    ◾ equals: 5        (kotlin.Int <1234789>)
◆ contains only, in order: 
  ✔ ▶ element 0: 1        (kotlin.Int <1234789>)
      ◾ equals: 1        (kotlin.Int <1234789>)
  ✔ ▶ element 1: 2        (kotlin.Int <1234789>)
      ◾ equals: 2        (kotlin.Int <1234789>)
  ✔ ▶ element 2: 2        (kotlin.Int <1234789>)
      ◾ equals: 2        (kotlin.Int <1234789>)
  ✘ ▶ element 3: 4        (kotlin.Int <1234789>)
      ◾ equals: 3        (kotlin.Int <1234789>)
  ✘ ▶ element 4: ❗❗ hasNext() returned false
        » equals: 4        (kotlin.Int <1234789>)

expect(listOf(1, 2, 2, 4)).toContain.inAnyOrder.atLeast(1).butAtMost(2).entries({ toBeLessThan(3) })

ExampleOutput

expected that subject: [1, 2, 2, 4]        (java.util.Arrays.ArrayList <1234789>)
◆ contains, in any order: 
  ⚬ an element which: 
      » is less than: 3        (kotlin.Int <1234789>)
    ⚬ ▶ number of such entries: 3
        ◾ is at most: 2

expect(listOf(1, 2, 2, 4)).toContain.inAnyOrder.only.values(1, 2, 3, 4)

ExampleOutput

expected that subject: [1, 2, 2, 4]        (java.util.Arrays.ArrayList <1234789>)
◆ contains only, in any order: 
  ✔ an element which equals: 1        (kotlin.Int <1234789>)
  ✔ an element which equals: 2        (kotlin.Int <1234789>)
  ✘ an element which equals: 3        (kotlin.Int <1234789>)
  ✔ an element which equals: 4        (kotlin.Int <1234789>)
  ❗❗ following elements were mismatched: 
     ⚬ 2        (kotlin.Int <1234789>)

expect(listOf(1, 2, 2, 4)).toContain.inAnyOrder.only.values(4, 3, 2, 2, 1)

ExampleOutput

expected that subject: [1, 2, 2, 4]        (java.util.Arrays.ArrayList <1234789>)
◆ ▶ size: 4        (kotlin.Int <1234789>)
    ◾ equals: 5        (kotlin.Int <1234789>)
◆ contains only, in any order: 
  ✔ an element which equals: 4        (kotlin.Int <1234789>)
  ✘ an element which equals: 3        (kotlin.Int <1234789>)
  ✔ an element which equals: 2        (kotlin.Int <1234789>)
  ✔ an element which equals: 2        (kotlin.Int <1234789>)
  ✔ an element which equals: 1        (kotlin.Int <1234789>)

Map Assertions

Map assertions are kind of very similar to Collection Assertions, also regarding reporting. That is the reason why we are not going into too much detail here because we assume you are already familiar with it.

We provide again Shortcut Functions for the most common scenarios and more Sophisticated Assertion Builder for the other cases.

Shortcut Functions

expect(mapOf("a" to 1, "b" to 2)).toContain("c" to 2, "a" to 1, "b" to 1)

ExampleOutput

expected that subject: {a=1, b=2}        (java.util.LinkedHashMap <1234789>)
◆ contains, in any order: 
  ⚬ ▶ entry "c": ❗❗ key does not exist
        » equals: 2        (kotlin.Int <1234789>)
  ⚬ ▶ entry "b": 2        (kotlin.Int <1234789>)
      ◾ equals: 1        (kotlin.Int <1234789>)

Next to making assertions based on key-value Pairs one can also define sub assertions for the value of an entry with the help of the parameter object KeyValue:

expect(mapOf("a" to 1, "b" to 2)).toContain(
    KeyValue("c") { toEqual(2) },
    KeyValue("a") { toBeGreaterThan(2) },
    KeyValue("b") { toBeLessThan(2) }
)

ExampleOutput

expected that subject: {a=1, b=2}        (java.util.LinkedHashMap <1234789>)
◆ contains, in any order: 
  ⚬ ▶ entry "c": ❗❗ key does not exist
        » equals: 2        (kotlin.Int <1234789>)
  ⚬ ▶ entry "a": 1        (kotlin.Int <1234789>)
      ◾ is greater than: 2        (kotlin.Int <1234789>)
  ⚬ ▶ entry "b": 2        (kotlin.Int <1234789>)
      ◾ is less than: 2        (kotlin.Int <1234789>)

In case you expect that a map only contains certain entries, then you can use the shortcut toContainOnly. Again both overloads are provided, one for key-value Pairs:

expect(mapOf("a" to 1, "b" to 2)).toContainOnly("b" to 2)

ExampleOutput

expected that subject: {a=1, b=2}        (java.util.LinkedHashMap <1234789>)
◆ ▶ size: 2        (kotlin.Int <1234789>)
    ◾ equals: 1        (kotlin.Int <1234789>)
◆ contains only, in any order: 
  ✔ ▶ entry "b": 2        (kotlin.Int <1234789>)
      ◾ equals: 2        (kotlin.Int <1234789>)
    ❗❗ additional entries detected: 
       ⚬ entry "a": 1        (kotlin.Int <1234789>)

And the other overload which expects a KeyValue and allows defining sub asertions for the value:

expect(mapOf("a" to 1, "b" to 2)).toContainOnly(
    KeyValue("c") { toEqual(2) },
    KeyValue("a") { toBeLessThan(2) },
    KeyValue("b") { toBeLessThan(2) }
)

ExampleOutput

expected that subject: {a=1, b=2}        (java.util.LinkedHashMap <1234789>)
◆ ▶ size: 2        (kotlin.Int <1234789>)
    ◾ equals: 3        (kotlin.Int <1234789>)
◆ contains only, in any order: 
  ✘ ▶ entry "c": ❗❗ key does not exist
        » equals: 2        (kotlin.Int <1234789>)
  ✔ ▶ entry "a": 1        (kotlin.Int <1234789>)
      ◾ is less than: 2        (kotlin.Int <1234789>)
  ✘ ▶ entry "b": 2        (kotlin.Int <1234789>)
      ◾ is less than: 2        (kotlin.Int <1234789>)

Sophisticated Assertion Builders

Most functionality for Map.toContain are provided as shortcut functions but there is a handy one in case you deal with ordered Maps: .toContain.inOrder.only
There are multiple methods finalising the building process : entry/entries/entriesOf where entry and entries again provide two overloads, one expecting key-value Pairs:

expect(mapOf("a" to 1, "b" to 2)).toContain.inOrder.only.entries("b" to 2, "a" to 1)

ExampleOutput

expected that subject: {a=1, b=2}        (java.util.LinkedHashMap <1234789>)
◆ contains only, in order: 
  ✘ ▶ element 0: a=1        (java.util.LinkedHashMap.Entry <1234789>)
      ◾ ▶ key: "a"        <1234789>
          ◾ equals: "b"        <1234789>
      ◾ ▶ value: 1        (kotlin.Int <1234789>)
          ◾ equals: 2        (kotlin.Int <1234789>)
  ✘ ▶ element 1: b=2        (java.util.LinkedHashMap.Entry <1234789>)
      ◾ ▶ key: "b"        <1234789>
          ◾ equals: "a"        <1234789>
      ◾ ▶ value: 2        (kotlin.Int <1234789>)
          ◾ equals: 1        (kotlin.Int <1234789>)

And the other expecting KeyValues which allow specifying sub assertions for the value

expect(mapOf("a" to 1, "b" to 2)).toContain.inOrder.only.entries(
    KeyValue("a") { toBeLessThan(2) },
    KeyValue("b") { toBeLessThan(2) })

ExampleOutput

expected that subject: {a=1, b=2}        (java.util.LinkedHashMap <1234789>)
◆ contains only, in order: 
  ✔ ▶ element 0: a=1        (java.util.LinkedHashMap.Entry <1234789>)
      ◾ ▶ key: "a"        <1234789>
          ◾ equals: "a"        <1234789>
      ◾ ▶ value: 1        (kotlin.Int <1234789>)
          ◾ is less than: 2        (kotlin.Int <1234789>)
  ✘ ▶ element 1: b=2        (java.util.LinkedHashMap.Entry <1234789>)
      ◾ ▶ key: "b"        <1234789>
          ◾ equals: "b"        <1234789>
      ◾ ▶ value: 2        (kotlin.Int <1234789>)
          ◾ is less than: 2        (kotlin.Int <1234789>)

Others

In case you want to postulate an assertion about a value of one particular key, then you can use getExisting. For instance:

data class Person(val firstName: String, val lastName: String, val age: Int)
val bernstein = Person("Leonard", "Bernstein", 50)
expect(mapOf("bernstein" to bernstein))
    .getExisting("bernstein") {
        feature { f(it::firstName) }.toEqual("Leonard")
        feature { f(it::age) }.toEqual(60)
    }
    .getExisting("einstein") {
        feature { f(it::firstName) }.toEqual("Albert")
    }

ExampleOutput

expected that subject: {bernstein=Person(firstName=Leonard, lastName=Bernstein, age=50)}        (java.util.Collections.SingletonMap <1234789>)
◆ ▶ get("bernstein"): Person(firstName=Leonard, lastName=Bernstein, age=50)        (readme.examples.MostExamplesSpec$1$Person <1234789>)
    ◾ ▶ age: 50        (kotlin.Int <1234789>)
        ◾ equals: 60        (kotlin.Int <1234789>)

In case you want to make an assertion only about the keys or values of the Map then you can use keys or values:

expect(mapOf("a" to 1, "b" to 2)) {
    keys { toHaveElementsAndAll { toStartWith("a") } }
    values { toHaveElementsAndNone { toBeGreaterThan(1) } }
}

ExampleOutput

expected that subject: {a=1, b=2}        (java.util.LinkedHashMap <1234789>)
◆ ▶ keys: [a, b]        (java.util.LinkedHashMap.LinkedKeySet <1234789>)
    ◾ all entries: 
        » starts with: "a"        <1234789>
        ❗❗ following elements were mismatched: 
           ⚬ index 1: "b"        <1234789>
◆ ▶ values: [1, 2]        (java.util.LinkedHashMap.LinkedValues <1234789>)
    ◾ does not contain: 
      ⚬ an element which: 
          » is greater than: 1        (kotlin.Int <1234789>)
          ❗❗ following elements were mismatched: 
             ⚬ index 1: 2        (kotlin.Int <1234789>)

Last but not least, you can use the non-reporting asEntries() function which turns Expect<Map<K, V>> into an Expect<Set<Map.Entry<K, V>> and thus allows that you can use all the assertion functions and sophisticated builders shown in Collection Assertions.

There should seldom be a need for it but in case you want to make also sub assertions for the key, then it will come in handy:

expect(linkedMapOf("a" to 1, "b" to 2)).asEntries().toContain.inOrder.only.entries(
    { toEqualKeyValue("a", 1) },
    {
        key.toStartWith("a")
        value.toBeGreaterThan(2)
    }
)

ExampleOutput

expected that subject: {a=1, b=2}        (java.util.LinkedHashMap <1234789>)
◆ contains only, in order: 
  ✔ ▶ element 0: a=1        (java.util.LinkedHashMap.Entry <1234789>)
      ◾ ▶ key: "a"        <1234789>
          ◾ equals: "a"        <1234789>
      ◾ ▶ value: 1        (kotlin.Int <1234789>)
          ◾ equals: 1        (kotlin.Int <1234789>)
  ✘ ▶ element 1: b=2        (java.util.LinkedHashMap.Entry <1234789>)
      ◾ ▶ key: "b"        <1234789>
          ◾ starts with: "a"        <1234789>
      ◾ ▶ value: 2        (kotlin.Int <1234789>)
          ◾ is greater than: 2        (kotlin.Int <1234789>)

toEqualKeyValue as well as key and value are assertion functions defined for Map.Entry<K, V>.

There are more assertion functions, a full list can be found in KDoc of atrium-api-fluent-en_GB.

Path Assertions

Atrium’s assertions for paths give detailed failure hints explaining what happened on the file system. For example, exists will explain which entry was the first one missing:

expect(Paths.get("/usr/bin/noprogram")).toExist()

ExampleOutput

expected that subject: /usr/bin/noprogram        (sun.nio.fs.UnixPath <1234789>)
◆ to: exist
    » the closest existing parent directory is /usr/bin

Atrium will give details about why something cannot be accessed, for example when checking whether a file is writable:

expect(Paths.get("/root/.ssh/config")).toBeWritable()

ExampleOutput

expected that subject: /root/.ssh/config        (sun.nio.fs.UnixPath <1234789>)
◆ is: writable
    » failure at parent path: /root        (sun.nio.fs.UnixPath <1234789>)
      » access was denied
      » the owner is root, the group is root
      » the permissions are u=rwx g= o=

Even in more complicated scenarios, Atrium explains step by step what happened:

val directory = Files.createDirectory(tmpdir.resolve("atrium-path"))
val file = Files.createFile(directory.resolve("file"))
val filePointer = Files.createSymbolicLink(directory.resolve("directory"), file)

expect(filePointer.resolve("subfolder/file")).toBeARegularFile()

ExampleOutput

expected that subject: /tmp/atrium-path/directory/subfolder/file        (sun.nio.fs.UnixPath <1234789>)
◆ is: a file
    » followed the symbolic link /tmp/atrium-path/directory to /tmp/atrium-path/file
    » failure at parent path: /tmp/atrium-path/file        (sun.nio.fs.UnixPath <1234789>)
      » was a file instead of a directory

Attaching a Reason

In case you want to add further information to an assertion, e.g. state the reason why you expect it to hold, you can use because:

expect("filename?")
    .because("? is not allowed in file names on Windows") {
        notToContain("?")
    }

ExampleOutput

expected that subject: "filename?"        <1234789>
◆ does not contain: 
  ⚬ value: "?"        <1234789>
    ⚬ ▶ number of matches: 1
        ◾ is: 0        (kotlin.Int <1234789>)
ℹ because: ? is not allowed in file names on Windows
💬 Use because only to give reasons for non-obvious assertions

because can be a useful tool for explaining why there is a certain assertion. Sometimes it is not directly obvious why one should expect something. In such cases, using because can make your code, and your error messages, easier to understand for other developers (including yourself in three months).

Having said that, you should not use because if you are missing a specific predefined assertion function. You can use a feature assertion, write your own expectation function or propose an addition to Atrium in such cases.

Just like code comments, because can be valuable, but should not be overused.

Data Driven Testing

Atrium is not intended for data driven testing in the narrowed sense in terms that it cannot produce multiple tests. This is the responsibility of your test runner. However, Atrium let you define multiple assertions within one test and reports them all if you want. In this sense it can be used for data driven testing. This is especially helpful in case your test runner does not support data driven testing (or other mechanisms like hierarchical or dynamic tests). As an example, Atrium can help you writing data driven tests in a common module of a multiplatform-project.

The trick is to wrap your assertions into an assertion group block and create Feature Assertions. Following an example:

fun myFun(i: Int) = (i + 97).toChar()

expect("calling myFun with...") {
    mapOf(
        1 to 'a',
        2 to 'c',
        3 to 'e'
    ).forEach { (arg, result) ->
        feature { f(::myFun, arg) }.toEqual(result)
    }
}

ExampleOutput

expected that subject: "calling myFun with..."        <1234789>
◆ ▶ myFun(1): 'b'
    ◾ equals: 'a'
◆ ▶ myFun(3): 'd'
    ◾ equals: 'e'

Depending on the chosen reporting style it will only show the failing cases (default behaviour). This is also the reason why the call of myFun(2) is not listed (as the result is c as expected).

Please create a feature request if you want to see a summary, meaning also successful assertions -- we happily add more functionality if it is of use for someone.

Following another example which involves an assertion creator lambda and not only a simple toEqual check. We are going to reuse the myFun from above:

import ch.tutteli.atrium.logic.utils.expectLambda

expect("calling myFun with ...") {
    mapOf(
        1 to expectLambda<Char> { toBeLessThan('f') },
        2 to expectLambda { toEqual('c') },
        3 to expectLambda { toBeGreaterThan('e') }
    ).forEach { (arg, assertionCreator) ->
        feature({ f(::myFun, arg) }, assertionCreator)
    }
}

ExampleOutput

expected that subject: "calling myFun with ..."        <1234789>
◆ ▶ myFun(3): 'd'
    ◾ is greater than: 'e'

The example should be self-explanatory. One detail to note though is the usage of expectLambda. It is a helper function which circumvents certain Kotlin type inference bugs (upvote them please). Writing the same as mapOf<Int, Expect<Char>.() -> Unit>( 1 to { ... } ) would not work as the type for a lambda involved in a Pair is not (yet) inferred correctly by Kotlin.

There is one last function worth mentioning here which comes in handy in data-driven testing in case the subject has a nullable type

If you wish to make sub-assertions on the non-nullable type of the subject, then you can use toEqualNullIfNullGivenElse which accepts an assertionCreator-lambda or null. It is short for if (assertionCreatorOrNull == null) toEqual(null) else notToEqual(assertionCreatorOrNull). Following another fictional example which illustrates toEqualNullIfNullGivenElse (we are reusing myFun from above):

fun myNullableFun(i: Int) = if (i > 0) i.toString() else null

expect("calling myNullableFun with ...") {
    mapOf(
        Int.MIN_VALUE to expectLambda<String> { toContain("min") },
        -1 to null,
        0 to null,
        1 to expectLambda { toEqual("1") },
        2 to expectLambda { toEndWith("2") },
        Int.MAX_VALUE to expectLambda { toEqual("max") }
    ).forEach { (arg, assertionCreatorOrNull) ->
        feature { f(::myNullableFun, arg) }.toEqualNullIfNullGivenElse(assertionCreatorOrNull)
    }
}

ExampleOutput

expected that subject: "calling myNullableFun with ..."        <1234789>
◆ ▶ myNullableFun(-2147483648): null
      » contains: 
        ⚬ value: "min"        <1234789>
            » but no match was found
◆ ▶ myNullableFun(2147483647): "2147483647"        <1234789>
    ◾ equals: "max"        <1234789>

Further Examples

Atrium supports further assertion builders (e.g, for CharSequence) as well as assertion functions which have not been shown in the examples. Have a look at apis/differences.md for a few more examples. This site contains also a list of all APIs with links to their assertion function catalogs.

You can also have a look at the specifications for more examples.

Sample Projects

Have a look into the samples folder, it currently contains sample projects for

Are you using a different runner? A PR would be appreciated 😊 .

Third-party Extensions

Following extensions are maintained outside of this repository.

How is Atrium different from other Assertion Libraries

The following subsections shall give you a quick overview how Atrium differ from other assertion libraries.

Ready to Help

Atrium is designed to help you whenever possible. We think this is the biggest difference to other assertion libraries and a very handy one indeed.

1. Fluent API with Code Documentation

Atrium provides a fluent API where the design focus was put on the interoperability (of the API) with the code completion functionality of your IDE. Or in other words, you can always use code completion to get direct help from your IDE. This experience is improved by providing up-to-date code documentation (in form of KDoc) for all assertion functions, so that you get the extra help needed.

💩 <- this icon signifies a bug in Kotlin which you might encounter as well. We try to provide a workaround whenever possible.

💩 There is no KDoc for toEqual

There is, but IntelliJ will not show it to you due to this bug (please upvote it). You should be able to see the KDoc of other functions without problems. But in case, you can also browse the online documentation, e.g. KDoc of toEqual.

2. Additional Information in Failure Reporting

Atrium adds extra information to error messages so that you get quickly a better idea of what went wrong. For instance, for the following assertion (which fails):

expect(listOf(1, 2, 3)).toContain.inOrder.only.values(1, 3)

Atrium points out which values were found, makes an implicit assertion about the size and also states which entries were additionally contained in the list:

expected that subject: [1, 2, 3]        (java.util.Arrays.ArrayList <1234789>)
◆ ▶ size: 3        (kotlin.Int <1234789>)
    ◾ equals: 2        (kotlin.Int <1234789>)
◆ contains only, in order: 
  ✔ ▶ element 0: 1        (kotlin.Int <1234789>)
      ◾ equals: 1        (kotlin.Int <1234789>)
  ✘ ▶ element 1: 2        (kotlin.Int <1234789>)
      ◾ equals: 3        (kotlin.Int <1234789>)
    ❗❗ additional elements detected: 
       ⚬ element 2: 3        (kotlin.Int <1234789>)

Let us have a look at another example.

expect(9.99f).toEqualWithErrorTolerance(10.0f, 0.01f)

The above assertion looks good at first sight but actually fails (at least on my machine). And without some extra information in the output we would believe that there is actually a bug in the assertion library itself. But Atrium shows where it goes wrong and even gives a possible hint:

expected that subject: 9.99        (kotlin.Float <1234789>)
◆ to equal (error ± 0.01): 10.0        (kotlin.Float <1234789>)
    » failure might be due to using kotlin.Float, see exact check on the next line
    » exact check is |9.989999771118164 - 10.0| = 0.010000228881835938 ≤ 0.009999999776482582

One last example. This time about making an assertion that a certain Throwable is thrown but the assertion fails because it was the wrong one. Atrium comes with a very useful hint, it shows the actual exception:

expect {
    try {
        throw UnsupportedOperationException("not supported")
    } catch (t: Throwable) {
        throw IllegalArgumentException("no no no...", t)
    }
}.toThrow<IllegalStateException> { messageToContain("no no no") }

ExampleOutput

expected that subject: () -> kotlin.Nothing        (readme.examples.MostExamplesSpec$1$39$1 <1234789>)
◆ ▶ thrown exception when called: java.lang.IllegalArgumentException
    ◾ is instance of type: IllegalStateException (java.lang.IllegalStateException)
      » ▶ message: 
          ◾ is instance of type: String (kotlin.String) -- Class: java.lang.String
          ◾ contains: 
            ⚬ value: "no no no"        <1234789>
                » but no match was found
    ℹ Properties of the unexpected IllegalArgumentException
      » message: "no no no..."        <1234789>
      » stacktrace: 
        ⚬ readme.examples.MostExamplesSpec$1$39$1.invoke(MostExamplesSpec.kt:248)
        ⚬ readme.examples.MostExamplesSpec$1$39$1.invoke(MostExamplesSpec.kt:22)
        ⚬ readme.examples.MostExamplesSpec$1$39.invoke(MostExamplesSpec.kt:272)
        ⚬ readme.examples.MostExamplesSpec$1$39.invoke(MostExamplesSpec.kt:22)
      » cause: java.lang.UnsupportedOperationException
          » message: "not supported"        <1234789>
          » stacktrace: 
            ⚬ readme.examples.MostExamplesSpec$1$39$1.invoke(MostExamplesSpec.kt:246)

3. Prevents you from Pitfalls

But not enough. There are certain pitfalls when it comes to using an assertion library and Atrium tries to prevent you from those.

For instance, an overload of toEqual and of notToEqual for BigDecimal was introduced which are both deprecated and throw a PleaseReplaceException. The reason behind it? It is very likely that a user actually wants to compare that a certain BigDecimal is numerically (not) equal to another BigDecimal rather than including BigDecimal.scale in the comparison. Accordingly, the deprecation message of toEqual (notToEqual alike) explains the problem and suggests to either use toEqualNumerically or toEqualIncludingScale. And if the user should decide to use toEqualIncludingScale and at some point an assertion fails only due to the comparison of BigDecimal.scale then Atrium reminds us of the possible pitfall. For instance:

expect(BigDecimal.TEN).toEqualIncludingScale(BigDecimal("10.0"))

ExampleOutput

expected that subject: 10        (java.math.BigDecimal <1234789>)
◆ is equal (including scale): 10.0        (java.math.BigDecimal <1234789>)
    ℹ notice, if you used toEqualNumerically then the assertion would have hold.

Another example are empty assertionCreator-lambdas. Getting distracted by a working colleague and taking up the work at the wrong position might sound familiar to you. For instance:

expect(listOf(1)).get(0) {}

ExampleOutput

expected that subject: [1]        (java.util.Collections.SingletonList <1234789>)
◆ ▶ get(0): 1        (kotlin.Int <1234789>)
    ◾ at least one assertion defined: false
        » You forgot to define assertions in the assertionCreator-lambda
        » Sometimes you can use an alternative to `{ }` For instance, instead of `toThrow<..> { }` you should use `toThrow<..>()`

Flexibility

Another design goal of Atrium was to give you the flexibility needed but still adhere to a concise design. First and most importantly, Atrium does not enforce a certain style on your code base. Quite the contrary, it gives you the flexibility to choose a desired name for the expectation verb, it continues by providing the possibility to configure the reporting style, goes on that you can choose from different API Styles and ends that you can replace almost all components by other implementations and hook into existing.

So for instance, if you like to use an infix API, then use the bundle atrium-infix-en_GB. You prefer pure fluent and do not even want to see infix style in your code, then use atrium-fluent-en_GB which provides a pure fluent style API.

You are free to choose what fits best without introducing ambiguity etc. You could even mix up different API-styles if needed (but not without losing conciseness -- but hey, it is your decision 😉 ).

Migration of Deprecated Functionality

Atrium follows Semantic Versioning and tries to be binary backward compatible within a major version (since 0.6.0). Until 1.0.0 this is only true for the API level, we reserve the right to break things on the logic and core level until then. Moreover, we follow the principle that a user of Atrium has enough time to migrate its code to new functionality before a next major version. We provide this in form of @Deprecated annotations with a corresponding ReplaceWith as well as migration guides in the Release Notes. This way we hope that we provide a pleasant way to stay up-to-date without the need to migrate everything from one day to the other.

Internationalization

The last difference is not yet fully-blown implemented, but the design of Atrium has everything needed to go down the planed Roadmap. Might well be that this topic is not really a concern of yours; unless...

  • you are using domain-driven-design and would like to adopt the ubiquitous language also to your test code.
  • you want to document the results of your defined assertions (in different languages)

Atrium already supports APIs in two languages, and it is an easy task to translate an API to another language (hello DDD-people 👋 you are good to go). Moreover, it is already possible to generate the output in a different language than the used API (e.g. code in English but report in German).

Together with the HTML-Report feature (currently missing but will follow) you will be able to generate reports in different languages. Already the HTML-Report feature as such might be of your interest. You can use it to document your user stories etc (almost) for free. In case you have clients who speak different languages then the HTML-Report together with the i18n feature will be especially helpful. We should not go on here, the HTML-Report feature is not yet implemented, but you can see what kind of road we plan to go down to.

Write own Assertion Functions

Are you missing an assertion function for a specific type and the generic Feature Assertions are not good enough?

The following subsections will show how you can write your own assertion functions. A pull request of your new assertion function is very much appreciated.

Boolean based Assertions

This is kind of the simplest way of defining assertion functions. Following an example:

import ch.tutteli.atrium.logic._logic

fun Expect<Int>.toBeAMultipleOf(base: Int) =
    _logic.createAndAppend("is multiple of", base) { it % base == 0 }

and its usage:

expect(12).toBeAMultipleOf(5)

ExampleOutput

expected that subject: 12        (kotlin.Int <1234789>)
◆ is multiple of: 5        (kotlin.Int <1234789>)

Let us see how we actually defined toBeAMultipleOf.

  1. Choose the extension point: in our example we want to provide the assertion function for Ints. Hence we define toBeAMultipleOf as extension function of Expect<Int>.

  2. Use the method createAndAddAssertion (provided by Expect) which creates and adds the assertion to itself (creating alone is not enough, it needs to be added in order that it is evaluated). The method createAndAddAssertion returns itself (the same Expect) making it easy for you to provide a fluent API as well.

    The method createAndAddAssertion expects:

    • a either a String or a Translatable as description of your assertion.
    • the representation of the expected value.
    • and the actual check as lambda where you typically use it which refers to the subject of the assertion.

We used a String as description in the above example because we are not bothered with internationalization at this point (have a look at Internationalization if you are).

In most cases you probably use the expected value itself as its representation -- so you pass it as second argument. And finally you specify the test as such in the lambda passed as third argument.

But not all assertion functions require a value which is somehow compared against the subject -- some make an assertion about a characteristic of the subject without comparing it against an expected value. Consider the following assertion function:

import ch.tutteli.atrium.logic._logic

fun Expect<Int>.toBeEven() =
    _logic.createAndAppend("is", Text("an even number")) { it % 2 == 0 }

We are using a Text here so that "an even number" is not treated as a String in reporting. Its usage looks then as follows:

expect(13).toBeEven()

ExampleOutput

expected that subject: 13        (kotlin.Int <1234789>)
◆ is: an even number

Compose Assertion Functions

So far, we core contributors ran quickly into the situation where we wanted to compose functions or reuse existing functions but with different arguments. We will show both use cases here, starting off by composing functions.

Say you want to build a toBeBetween assertion function for java.util.Date, you could write it as follows:

fun <T : Date> Expect<T>.toBeBetween(lowerBoundInclusive: T, upperBoundExclusive: T) =
    toBeGreaterThanOrEqualTo(lowerBoundInclusive).and.toBeLessThan(upperBoundExclusive)

Pretty simple, isn't it? Notice though, that this function fails fast, which means, the upper bound is not evaluated if the assertion about the lower bound already fails. You need to use an assertion group block if you want that both are evaluated:

import ch.tutteli.atrium.logic._logic

fun <T : Date> Expect<T>.toBeBetween(lowerBoundInclusive: T, upperBoundExclusive: T) =
    _logic.appendAsGroup {
        toBeGreaterThanOrEqualTo(lowerBoundInclusive)
        toBeLessThan(upperBoundExclusive)
    }

Still simple enough.

💬 Why is a type parameter used in the above examples?

That is right, we used a type parameter T: Date and not Expect<Date> directly. You should always do this unless your type is final (not open) and does not have type parameters itself - but to have a simple rule, just do it. This way the assertion function is also available for sub types. This is because Expect is invariant. Following an example:

interface A { val foo get() = 1 }
class B: A
val Expect<A>.foo get() = feature(A::foo)

expect(B()).foo // does not compile as foo is only available for `Expect<A>`

So let's move on to an example which is a bit more complicated. Assume the following data class Person

data class Person(
    val firstName: String,
    val lastName: String,
    val age: Int,
    val children: Collection<Person>
    // ...  and others
)

Say you want to make an assertion about the number of children a person has:

fun Expect<Person>.toHaveNumberOfChildren(number: Int): Expect<Person> =
    feature(Person::children) { toHaveSize(number) }

Three things to notice here:

  1. we make use of a feature assertion with class reference.
  2. We use the overload which expects an assertionCreator-lambda. This way subsequent assertions are still made on Person and not on children.

Its usage is then as follows:

expect(Person("Susanne", "Whitley", 43, listOf()))
    .toHaveNumberOfChildren(2)

ExampleOutput

expected that subject: Person(firstName=Susanne, lastName=Whitley, age=43, children=[])        (readme.examples.Person <1234789>)
◆ ▶ children: []        (kotlin.collections.EmptyList <1234789>)
    ◾ ▶ size: 0        (kotlin.Int <1234789>)
        ◾ equals: 2        (kotlin.Int <1234789>)

Another example: assert the person has children which are all adults (assuming 18 is the age of majority).

fun Expect<Person>.toHaveAdultChildren(): Expect<Person> =
    feature(Person::children) {
        toHaveElementsAndAll {
            feature(Person::age).toBeGreaterThanOrEqualTo(18)
        }
    }

We once again use feature with an assertion group block for the same reason as above. Note how toHaveElementsAndAll already checks that there is at least one element. I.e. it fails for a Person with 0 children, because such a person does not have adult children.

expect(Person("Susanne", "Whitley", 43, listOf()))
    .toHaveAdultChildren()

ExampleOutput

expected that subject: Person(firstName=Susanne, lastName=Whitley, age=43, children=[])        (readme.examples.Person <1234789>)
◆ ▶ children: []        (kotlin.collections.EmptyList <1234789>)
    ◾ has: a next element
      » all entries: 
          » ▶ age: 
              ◾ is greater than or equal to: 18        (kotlin.Int <1234789>)

If we keep adding assertion functions involving children it might be best to provide a shortcut property and function.

val Expect<Person>.children: Expect<Collection<Person>> get() = feature(Person::children)

fun Expect<Person>.children(assertionCreator: Expect<Collection<Person>>.() -> Unit): Expect<Person> =
    feature(Person::children, assertionCreator)

Notice, that we have used a class-reference and not a bounded-reference to refer to children which is best practice (see feature assertions within assertion functions). With this, we can write things like:

expect(Person("Susanne", "Whitley", 43, listOf(Person("Petra", "Whitley", 12, listOf()))))
    .children { // using the fun -> assertion group, ergo sub-assertions don't fail fast
        toHaveElementsAndNone {
            feature { f(it::firstName) }.toStartWith("Ro")
        }
        toHaveElementsAndAll {
            feature { f(it::lastName) }.toEqual("Whitley")
        }
    } // subject is still Person here
    .apply { // only evaluated because the previous assertion group holds
        children  // using the val -> subsequent assertions are about children and fail fast
            .toHaveSize(2)
            .toHaveElementsAndAny {
                feature { f(it::age) }.toBeGreaterThan(18)
            }
    } // subject is still Person here due to the `apply`
    .children // using the val -> subsequent assertions are about children and fail fast
    .toHaveSize(2)

ExampleOutput

expected that subject: Person(firstName=Susanne, lastName=Whitley, age=43, children=[Person(firstName=Petra, lastName=Whitley, age=12, children=[])])        (readme.examples.Person <1234789>)
◆ ▶ children: [Person(firstName=Petra, lastName=Whitley, age=12, children=[])]        (java.util.Collections.SingletonList <1234789>)
    ◾ ▶ size: 1        (kotlin.Int <1234789>)
        ◾ equals: 2        (kotlin.Int <1234789>)

Enough of feature assertions. Let's move on to an example where we want to reuse an existing function but with different arguments. Say we have a function which returns a list of first name / last name Pairs. We want to assert that the pairs contain only the first name / last name pairs of certain Persons in any order. Collection Assertions will help us with this. However, toContain.inAnyOrder.values expects Pairs. So we have to map from Person to Pair upfront. As we have a variable length argument list and want to pass it to a variable length argument list, this cannot be done with a simple map from Kotlin. And it gets worse if we want to use toContain.inAnyOrder.entries which expects at least one assertionCreator-lambda (Expect<T>.() -> Unit) because Kotlin cannot infer the types automatically.

mapArguments to the rescue, you can write the assertion function as follows:

import ch.tutteli.atrium.logic.utils.mapArguments

fun <T : List<Pair<String, String>>> Expect<T>.areNamesOf(
    person: Person, vararg otherPersons: Person
): Expect<T> {
    val (pair, otherPairs) = mapArguments(person, otherPersons) { it.firstName to it.lastName }
    return toContain.inAnyOrder.only.values(pair, *otherPairs)
}

As you can see we moved the mapping inside the function so that the consumer of our API can happily use it as follows:

expect(get...WhichReturnsPairs()).areNamesOf(fKafka, eBloch, kTucholsky)

Another fictional example, say we want to assert that the pairs have the same initials as the given persons and in the given order. Which means, this time we need to use assertionCreator-lambdas. This can be written as follows:

fun <T : List<Pair<String, String>>> Expect<T>.sameInitialsAs(
    person: Person, vararg otherPersons: Person
): Expect<T> {
    val (first, others) = mapArguments(person, otherPersons).toExpect<Pair<String, String>> {
        first.toStartWith(it.firstName[0].toString())
        second.toStartWith(it.lastName[0].toString())
    }
    return toContain.inOrder.only.entries(first, *others)
}

There are a few additional methods which you can call after mapArguments. See KDoc of ArgumentMapperBuilder. In case you want to provide your own implementation, it suffices to create an extension function for ArgumentMapperBuilder.

Enhanced Reporting

Composing assertion functions give already quite a bit of power to an assertion function writer. Yet, sometimes we would like to create functions which have a better error reporting than the one we get when we compose assertion functions.

_logic is the entry point to AssertionContainer which is the equivalent of Expect but on a lower level.

Following a quick overview what extension methods could be useful:

  • all assertion functions on the logic level (what you have seen in Compose-assertion-functions was the API level) so that you can reuse and compose them in other ways.
  • changeSubject which allows to change the subject either:
    • unreported; meaning it does not show up in reporting (e.g. Expect<Array<out T>>.asList() uses it, see arrayAssertions)
    • reported, using reportBuilder; meaning a subject transformation which is shown in reporting as it incorporates a transformation (e.g. toBeA uses it, see AnyAssertions)
  • collect which allows to collect assertions - especially helpful in composing assertions (see mapAssertions)
  • extractFeature for feature assertions which are not always save to extract (see List.get)

Besides, the assertionBuilder allows to create different kinds of assertions (see AssertionBuilder for more information) which can be used to create very specific assertion functions.

You can find an example in floatingPointAssertions which makes use of explanatory assertions as well as providing a failure hint.

Unfortunately we do not have the time to cover all cases, so let us know if you want to know more -- either by opening an issue or via the atrium Slack channel (Invite yourself).

Own Sophisticated Assertion Builders

Do you want to write an own sophisticated assertion builder (or extend a current with more options) instead of an assertion function? Great, we do not provide documentation yet (had only one question about it since 2017).

We are willing to provide more documentation if you need it (please open an issue). In the meantime we might help you via slack, please post your questions in the atrium Slack channel (Invite yourself in case you do not have an account yet).

Use own Expectation Verb

Atrium offers the expectation verb expect out of the box.

You can also define your own expectation verb if expect does not suite you or in case you want to change some default implementation. In order to create an own expectation verb it is sufficient to:

  1. Copy the file content of atriumVerbs.kt
  2. Create your own atriumVerbs.kt and paste the previously copied content -- notice that you can also use a String for the expectation verb in case you do not care about Internationalization
  3. Adjust package name and imports and rename expect as desired (you can also leave it that way of course).
  4. exclude atrium-verbs from your dependencies. Taking the setup shown in the Installation section for the JVM platform, you would replace the dependencies block as follows:
    dependencies {
        testImplementation("ch.tutteli.atrium:atrium-fluent-en_GB:$atrium_version") {
            exclude group: 'ch.tutteli.atrium', module: 'atrium-verbs'
        }
    }

What are the benefits of creating an own expectation verb:

  • you can encapsulate the reporting style.
    This is especially useful if you have multiple projects and want to have a consistent reporting style.
    For instance, you could change from same-line to multi-line reporting or report not only failing but also successful assertions, change the output language etc.

    💬 where should I put the atriumVerbs.kt?

    We suggest you create an adapter project for Atrium where you specify the expectation verb. And most likely you will accumulate them with assertion functions which are so common that they appear in multiple projects -- please share them with us (get in touch with us via issue or slack) if they are not of an internal nature 😉


  • you can define some default configurations like show only failing assertions for toContain.inOrder.only

What are the drawbacks:

  • you have to maintain your expectation verb. That should not be a big deal though -- you might have to replace deprecated options by their replacement when you upgrade to a newer Atrium version but that's about it.

Use own Components

Replacing existing components with your own (or third-party) components can be done when specifying an own expectation verb via withOptions. See for instance atriumVerbs.kt which is used internally of Atrium in tests and uses a different AtriumErrorAdjuster.

Another example, say you prefer multi-line reporting over single-line reporting, then you can use withOptions as follows:

import ch.tutteli.atrium.core.ExperimentalNewExpectTypes
import ch.tutteli.atrium.creating.ExperimentalComponentFactoryContainer
import ch.tutteli.atrium.creating.build

@OptIn(ExperimentalNewExpectTypes::class, ExperimentalComponentFactoryContainer::class)
fun <T> expect(subject: T): RootExpect<T> =
    RootExpectBuilder.forSubject(subject)
        .withVerb("expected the subject")
        .withOptions {
            withComponent(TextAssertionPairFormatter::class) { c ->
                TextAssertionPairFormatter.newNextLine(c.build(), c.build())
            }
        }
        .build()

Following an example using the expectation verb

expect(10).toEqual(9)

ExampleOutput

expected the subject:
  10        (kotlin.Int <1234789>)
◆ equals:
  9        (kotlin.Int <1234789>)

Compare the above output with what we would get per default:

expected the subject: 10        (kotlin.Int <1234789>)
◆ to be: 9        (kotlin.Int <1234789>)

You prefer another reporting style but Atrium does not yet support it? Please let us know it by writing a feature request.

There are more options to choose from. Take a look at the DefaultComponentFactoryContainer to see the default configuration.

Internationalization

We distinguish between two use cases. You might want to generate the Report in a different language or/and you might want to use the API in a different language.

Report

Following on the example in Write own Assertion Functions we show here how you need to write the toBeAMultipleOf function, so that it supports i18n. This way the report could be generated in another language.

The difference lies in the first argument passed to createAndAddAssertion; we do no longer use a String but a proper Translatable.

import ch.tutteli.atrium.logic.*

fun Expect<Int>.toBeAMultipleOf(base: Int): Expect<Int> = _logic.run {
    append(
        createDescriptiveAssertion(DescriptionIntAssertion.TO_BE_A_MULTIPLE_OF, base) { it % base == 0 }
    )
}

enum class DescriptionIntAssertion(override val value: String) : StringBasedTranslatable {
    TO_BE_A_MULTIPLE_OF("to be a multiple of")
}

Typically, you would put DescriptionIntAssertion into an own module (jar) so that it could be replaced (with zero performance cost) by another language representation. For instance, atrium-fluent-en_GB-common uses atrium-translations-en_GB-common whereas tests of atrium-infix_en_GB-common uses atrium-translations-de_CH-common.

💬 Using a TranslationSupplier

Next to providing translations via code you can also use a TranslationSupplier based Translator by configuring the ReporterBuilder accordingly (e.g. use withDefaultTranslationSupplier instead of withoutTranslations). Atrium supports a properties files based TranslationSupplier for JVM (a supplier for JS will follow) which is more or less what ResourceBundle provides out of the box. Yet, a Translator uses a more enhanced fallback mechanism compared to a ResourceBundle. For further technical information have a look at the KDoc of Translator. Notice though, that we plan to move away from the ResourceBundle-inspired approach due to enconding problems and missing implementations on other platforms than JVM.

Notice, Atrium does not yet support the generation of multiple reports in the same test run. This might become handy if you want to generate an HTML report in different languages.
However, Atrium is designed to support this use case -- if you need this feature, then please let us know it by writing a feature request.



Let us rewrite the toBeEven assertion function from the section Write own Assertion Functions as second example:

import ch.tutteli.atrium.logic.*

fun Expect<Int>.toBeEven(): Expect<Int> = _logic.run {
    append(
        createDescriptiveAssertion(DescriptionBasic.IS, DescriptionIntAssertions.EVEN) { it % 2 == 0 }
    )
}

enum class DescriptionIntAssertions(override val value: String) : StringBasedTranslatable {
    EVEN("an even number")
}

Once again we have to wrap the text which we want to be able to exchange with another language into a Translatable. Notice also, that we are reusing a Translatable from DescriptionBasic.

API in a different Language

Following on the example in the previous section, we want to write toBeAMultipleOf in such a way that one cannot only generate a report in a different language but also that one can use the function itself in a different language. Or in other words, provide our API in a different language (the same applies if you want to provide another API style).

We split up the function in two parts: API and logic -- whereas the logic creates the assertion and the API provides a function for the user (the API as such) and merely appends the assertion created by the logic to the Expect.

Typically, you put the API function in one module (jar) and the logic in another (so that the API can be exchanged). In the logic module we define and extension method for AssertionContainer

import ch.tutteli.atrium.creating.AssertionContainer

fun AssertionContainer<Int>.toBeAMultipleOf(base: Int): Assertion =
    createDescriptiveAssertion(DescriptionIntAssertion.TO_BE_A_MULTIPLE_OF, base) { it % base == 0 }

In the above example we created a simple DescriptiveAssertion with the help of createDescriptiveAssertion defined on AssertionContainer. We pass in a description (TO_BE_A_MULTIPLE_OF), use base as representation of the assertion and defined a lambda which implements a test to define whether the assertion holds or not.

In the API module we define the extension function and append the assertion to the current Expect by using logicAppend and calling the extension function from the logic module within the corresponding lambda.

import ch.tutteli.atrium.logic.*

fun Expect<Int>.toBeAMultipleOf(base: Int): Expect<Int> =
    _logicAppend { toBeAMultipleOf(base) }

At first, this looks like a recursive call. But as explained, within the _logicAppend-lambda we are on the logic level and thus call the function we defined above (which just turns out to have the same name).

You are ready to go, creating an API in a different language -- e.g. in German -- is now only a routine piece of work:

import ch.tutteli.atrium.logic.*

fun Expect<Int>.istEinVielfachesVon(base: Int): Expect<Int> =
    _logicAppend { toBeAMultipleOf(base) }

API Styles

Atrium supports currently two API styles: pure fluent and infix. Both have their design focus on interoperability with code completion functionality of your IDE -- so that you can let your IDE do some of the work.

Atrium is built up by different modules and it is your choice which implementation you want to use. However, this is more intended for advanced user with special requirements. Atrium provides bundle modules which bundle API, logic, core, translation as well as predefined expectation verbs, so that you just have to have a dependency on one of those bundles (kind a bit like a BOM pom in the maven world):

Have a look at apis/differences.md for more information and to see how the API styles differ.

Java Interoperability

Atrium provides some helper functions in case you have to deal with Java Code where not all types are non-nullable. Platform types are turned into a non-nullable version per default (if possible).

Yet, that might not be what you want, especially if you know that certain functions return potentially null or in other words, the return type of those functions should be treated as nullable in Kotlin. Therefore, you want to turn the platform type into the nullable version.

You need to use a cast to do this. But depending on your return type this might be cumbersome especially if you deal with type parameters. Thus, Atrium provides the following functions to ease dealing with Java Code at least for some standard cases:

  • nullable turns a type into a nullable type.
  • nullableContainer turns an Iterable into an iterable with nullable element type, likewise it does the same for Array.
  • nullableKeyMap turns a Map into a map with a nullable key type.
  • nullableValueMap turns a Map into a map with a nullable value type.
  • nullableKeyValueMap turns a Map into a map with a nullable key and nullable value type.

KDoc - Code Documentation

The code documentation is generated with dokka and is hosted on github-pages: KDoc of atrium

Known Limitations

According to the YAGNI principle this library does not yet offer a lot of out-of-the-box assertion functions. More functions will follow but only if they are used somewhere by someone. So, let us know if you miss something by creating a feature request.

FAQ

You find frequently asked questions below. If your question is not answered below, then please do not hesitate and ask your question in the atrium Slack channel. In case you do not have an account for kotlinlang.slack.com yet, then please Invite yourself.

Are there toContain/toHaveNextAndAll/None/All expectation functions for Sequence/Array?

Atrium does not provide extension function applicable to Expect<Sequence<E>> (or Array) directly, because they would basically duplicate the functions available for Iterable<E>.
However, Atrium provides asIterable and asList so that you can turn Expect<Sequence<E>> into Expect<Iterable<E>> or Expect<List<E>>. An example:

expect(sequenceOf(1, 2, 3)).asIterable().toContain(2)

Likewise, you can turn an Expect<Array<E>>, Expect<DoubleArray> etc. into an Expect<List<E>> with asList.

Feel free vote for first class support for Array and Sequence in api-fluent.

💬 why do I not see anything about the transformation in reporting?

asIterable uses _logic.changeSubject.unreported internally which is intended for not showing up in reporting. If you would like that the transformation is reflected in reporting then you can use a regular feature assertion as follows:

expect(sequenceOf(1, 2, 3)).feature { f(it::asIterable) }.toContain(2)

Where do I find a list of all available functions?

Atrium provides KDoc for all APIs - have a look at their KDoc:

Problems in conjunction with feature

See Ambiguity Problems and Property does not exist.

Roadmap

The roadmap is maintained at atrium-roadmap. The milestones give you an overview of the planned (breaking) changes -- e.g. the changes for the next major version 1.0.0

You are invited to take part in the discussions related to design decisions, upcoming features and more. Bring in your own wishes and ideas into this process.

In case you are missing a particular assertion function in Atrium, then please open a Feature Request in this repository.

Contributors and contribute

Our thanks go to code contributors as well as other contributors (see acknowledgements in the release notes).

You are more than welcome to contribute as well:

  • star Atrium if you like it
  • open a bug or create a feature request
  • share your ideas via issue or slack
  • ask a question so that we better understand where Atrium needs to improve.
  • write a blog post about Atrium (e.g. about a feature you like) or a tutorial (let us know we happily link to your page)
  • share your assertion functions with the rest of us by creating a pull request (no need for i18n support or the like, we can augment your pull request).
  • have a look at the help wanted issues if you would like to code (ping us on Slack if there are not any).

Please have a look at CONTRIBUTING.md for further suggestions and guidelines.

Sponsors

We would like to thank the following sponsors for their support:

Do you want to become a sponsor as well? Great, have a look at the following GitHub sponsor profiles:

  • robstoll (Author and main contributor)

or ping @robstoll in the Slack-Channel if you would like to support the project in another way.

License

Atrium is licensed under EUPL 1.2.

Atrium is using:

Comments
  • Elaborate path assertions (existence, type, permissions)

    Elaborate path assertions (existence, type, permissions)

    Implements assertions for a path

    • being a directory
    • being a file
    • being readable
    • being writable

    also adapts the existing existence assertions.

    The implementation tries to give helpful hints about what actually happened in the file system, so users do not have to check themselves (checking might not even be possible). The implementation realises these hints

    • when the subject path does not exist, show the last existing parent directory
    • when one part of the subject’s path is a file, show
    • when one part of the subject’s path cannot be accessed, show this part and its permissions
    • show all symbolic links that have been involved in resolving the path.

    Please note that I was often not sure which type of Assertion to use. I appreciate input on this.

    Examples:

     assert: /tmp/spek10214843601528508958/test        (sun.nio.fs.UnixPath <1744723183>)
    ◆ to be: a file
      ⚬ was: a directory
    
    assert: /tmp/spek10474502568126922701/__linkTo_exist        (sun.nio.fs.UnixPath <1409474925>)
    ◆ to: exist
        » followed the symbolic link /tmp/spek10474502568126922701/__linkTo_exist to /tmp/spek10474502568126922701/startFile/i/dont/exist
        » failure at parent path: /tmp/spek10474502568126922701/startFile        (sun.nio.fs.UnixPath <1011844624>)
          » was a file instead of a directory
    
    assert: /tmp/spek12943295927624369491/a        (sun.nio.fs.UnixPath <1986769819>)
    ◆ to be: readable
          ❗❗ found a symbolic link loop: /tmp/spek12943295927624369491/a -> /tmp/spek12943295927624369491/b -> /tmp/spek12943295927624369491/a
        » access threw FileSystemException: /tmp/spek12943295927624369491/a: Zu viele Ebenen aus symbolischen Links or unable to access attributes of symbolic link
    
    assert: /tmp/spek12594952494284002728/startDir/i/dont/exist        (sun.nio.fs.UnixPath <327819564>)
    ◆ to be: a directory
        » failure at parent path: /tmp/spek12594952494284002728/startDir        (sun.nio.fs.UnixPath <165175844>)
          » access was denied
          » the POSIX permissions are u=w g= o=
    
    assert: /tmp/spek14050191638722983707/__linkTo_not-writable        (sun.nio.fs.UnixPath <1825779019>)
    ◆ to be: writable
        » followed the symbolic link /tmp/spek14050191638722983707/__linkTo_not-writable to /tmp/spek14050191638722983707/not-writable
        » a directory exists at this location, but it is not writable
        » the POSIX permissions are u=rx g= o=x
    
    assert: /tmp/spek11308856912373601693/startDir/i/dont/exist        (sun.nio.fs.UnixPath <125066784>)
    ◆ to be: a directory
        » no file system entry exists at this location
        » the closest existing parent directory is /tmp/spek11308856912373601693/startDir
    

    closes #111 closes #112


    I confirm that I have read the Contributor Agreements v1.0, agree to be bound on them and confirm that my contribution is compliant.

    opened by jGleitz 42
  • Overload `CharSequence.containsRegex` to expect `Regex`

    Overload `CharSequence.containsRegex` to expect `Regex`

    domain

    • [x] modify CharSequenceContainsAssertions, expect a List<Regex> instead of a List<String> for regex
    • [x] adopt CharSequenceAssertionsBuilder, add the new method but keep the one which expects List<String> and delegate to the new one
    • [x] adopt the signature of CharSequenceAssertionsImpl, keep only List<Regex>

    lib

    • [x] add an additional type parameter SC to CharSequenceContains.Searcher which represents the search criterion => use it for searchFor
      • [x] use Any as second type parameter in ch.tutteli.atrium.assertions.charsequence.contains.CharSequenceContains.Searcher
      • [x] use Regex for RegexSearcher and IgnoringCaseRegexSearcher
      • [x] use String for IndexSearcher and IgnoringCaseIndexSearcher
      • [x] adopt CharSequenceContainsAssertionCreator.search (use SC)
      • [x] adopt the signature of _containsRegex
      • [x] adopt createAssertionGroup and other functions in ch/tutteli/atrium/domain/robstoll/lib/creating/charsequence/contains/creators/creators.kt this means i.a. that we need to map expected from Any to String with toString() for _containsValues and _containsValuesIgnoringCase
      • [x] adopt ch.tutteli.atrium.assertions.charsequence.contains.CharSequenceContains.Searcher (use Any as second type parameter)

    api

    • [x] provide an additional overload for containsRegex in charSequenceAssertions which expects Regex instead of string (see existing fun)
    • [x] provide an additional overload for CharSequenceContains.CheckerOption<T, NoOpSearchBehaviour>.regex in charSequenceContainsCreators.kt which expects Regex
    • [x] add a RegexSpec to ch.tutteli.atrium.api.fluent.en_GB.CharSequenceContainsRegexAssertionsSpec (similar to StringSpec)

    I confirm that I have read the Contributor Agreements v1.0, agree to be bound on them and confirm that my contribution is compliant.

    opened by neelkamath 33
  • Codecov migration to marketplace app

    Codecov migration to marketplace app

    Hi, Tom from Codecov here.

    We noticed that you are using Codecov with fairly high frequency, and we’re so excited to see that! However, because you are not using our app, you may have experienced issues with uploading reports or viewing coverage information. This is due to rate-limiting issues from GitHub.

    In order to prevent any future outages, we ask that you move over to our GitHub app integration.

    The process is extremely simple and shouldn’t require more than a few clicks, and you should not expect any downtime. By moving to our app, you will no longer need an admin or separate account to manage the relationship with GitHub as the team bot.

    Let me know if you have any questions, or if I can help at all with this process.

    opened by thomasrockhu 31
  • Samples composite build

    Samples composite build

    Started following instructions How do I test the setup?

    In misc\tools\atrium-samples-test\build.gradle I simply copied the file from atrium-bc-test and renamed jvm to js hope its enough I think that this comment in this file is redundand, is it? //TODO tests are now mostly in the common module. However, we cannot unzip this to the jvm project but // require a common module for it. Probably easiest to introduce a second bc-test which is an MPP project // add(testJarSources, "$groupId:atrium-api-$apiName-common:$oldVersion:testsources") { // exclude group: '*' // }


    I confirm that I have read the Contributor Agreements v1.0, agree to be bound on them and confirm that my contribution is compliant.

    opened by assaflei 30
  • Create a sample project for Atrium + junit5

    Create a sample project for Atrium + junit5

    Platform (JVM, JS and/or Android): Jvm

    Code-Documentation related feature

    It would be nice if we have a working example similar to the one we created for Atrium + mocha (see misc/examples/js/mocha) but this time for Atrium + junit5 (so JVM and not JS) The example project shall show how to setup junit5 + atrium and include a simple test

    This task is a good first issue especially if you intend to set up Atrium + junit5 anyway

    Your first contribution?

    • Write a comment I'll work on this if you would like to take this issue over. This way we get the chance to revise the description in case things have changed in the meantime, we might give you additional hints and we can assign the task to you, so that others do not start as well.
    • See Your first code contribution for guidelines.
    • Do not hesitate to ask questions here or to contact us via Atrium's slack channel if you need help (Invite yourself in case you do not have an account yet).
    help wanted good first issue 
    opened by robstoll 30
  • merge jdk8 extensions to jvm

    merge jdk8 extensions to jvm

    Started working with the api-fluent classes module-info.java file can be deleted, right?


    I confirm that I have read the Contributor Agreements v1.0, agree to be bound on them and confirm that my contribution is compliant.

    opened by assaflei 27
  • android dex compilation

    android dex compilation

    Started working on the task @robstoll I noticed that getAndroidProjects() returns an empty list, is this correct?


    I confirm that I have read the Contributor Agreements v1.0, agree to be bound on them and confirm that my contribution is compliant.

    opened by assaflei 27
  • improve specs for iterable, use one time iterables

    improve specs for iterable, use one time iterables

    Platform (jvm, jdk8, js, android): all

    Code related feature

    Currently the specs use listOf which can be consumed many times. Might well be there is a hidden bug. Let us use one time consumable iterables instead to find out.

    enhancement help wanted 
    opened by robstoll 24
  • Atrium v0.7.0 java 7 and java 9 compile issue with module-info.java for Android Instrumentation Tests

    Atrium v0.7.0 java 7 and java 9 compile issue with module-info.java for Android Instrumentation Tests

    Affected Version: 0.7.0 Platform (JVM or JS): JVM (Android/Art/Dalvik)

    How to reproduce the problem

    try to run ./gradlew :app:assembleDebugAndroidTest There is compile issue.

    error processing /Users/myuser/.gradle/caches/modules-2/files-2.1/ch.tutteli.atrium/atrium-cc-en_GB-robstoll/0.7.0/f01425b9648391ee682607d945e55b377607ea6f/atrium-cc-en_GB-robstoll-0.7.0.jar
    java.lang.RuntimeException
    	at org.objectweb.asm.ClassVisitor.visitModule(ClassVisitor.java:148)
    	at org.objectweb.asm.ClassReader.readModule(ClassReader.java:731)
    	at org.objectweb.asm.ClassReader.accept(ClassReader.java:632)
    	at org.objectweb.asm.ClassReader.accept(ClassReader.java:500)
    	at com.android.builder.desugaring.DesugaringClassAnalyzer.analyze(DesugaringClassAnalyzer.java:144)
    	at com.android.builder.desugaring.DesugaringClassAnalyzer.analyzeJar(DesugaringClassAnalyzer.java:92)
    	at com.android.builder.desugaring.DesugaringClassAnalyzer.analyze(DesugaringClassAnalyzer.java:63)
    	at com.android.build.gradle.internal.transforms.DesugarIncrementalTransformHelper.lambda$getInitalGraphData$4(DesugarIncrementalTransformHelper.java:150)
    	at java.util.concurrent.ForkJoinTask$AdaptedCallable.exec(ForkJoinTask.java:1424)
    	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    	at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
    	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
    

    Describe the bug

    When I updated Atrium version to 0.7.0 it doesn't work anymore for Android specific (Instrumentation) Tests which runs on the device or emulator. I think the issue with D8 (https://developer.android.com/studio/command-line/d8) compiler of Java bytecode to dex format. When I try to run the same version of Atrium on the JVM it works well.

    Expected behaviour

    The project should be compiled (assembled) at least.

    opened by ultraon 24
  • Fix Most Gradle Deprecation Warnings & Update Wrapper

    Fix Most Gradle Deprecation Warnings & Update Wrapper

    This PR fixes Gradle deprecation warnings and should make atrium almost ready for Gradle 7.

    • remove remaining deprecated dependencies configurations
    • move from the seemingly unmaintained mooworks node plugin to the maintained fork (see https://github.com/srs/gradle-node-plugin/pull/349)
    • use npx tasks instead of installing node dependencies explicitly

    I could not fix one warning, which seems to be caused by an expectedBy dependency. I guess this is out of our control.

    Stacktrace of the remaining error
    The compile configuration has been deprecated for dependency declaration. This will fail with an error in Gradle 7.0. Please use the implementation configuration instead.
            at org.gradle.api.internal.artifacts.DefaultDependencySet.warnIfConfigurationIsDeprecated(DefaultDependencySet.java:74)
            at org.gradle.api.internal.artifacts.DefaultDependencySet.add(DefaultDependencySet.java:64)
            at org.gradle.api.internal.artifacts.DefaultDependencySet.add(DefaultDependencySet.java:36)
            at org.jetbrains.kotlin.gradle.plugin.KotlinPlatformImplementationPluginBase$apply$$inlined$forEach$lambda$1.execute(KotlinMultiplatformPlugin.kt:78)
            at org.jetbrains.kotlin.gradle.plugin.KotlinPlatformImplementationPluginBase$apply$$inlined$forEach$lambda$1.execute(KotlinMultiplatformPlugin.kt:51)
            at org.gradle.api.internal.DefaultCollectionCallbackActionDecorator$BuildOperationEmittingAction$1$1.run(DefaultCollectionCallbackActionDecorator.java:100)
            at org.gradle.configuration.internal.DefaultUserCodeApplicationContext.reapply(DefaultUserCodeApplicationContext.java:60)
            at org.gradle.api.internal.DefaultCollectionCallbackActionDecorator$BuildOperationEmittingAction$1.run(DefaultCollectionCallbackActionDecorator.java:97)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:402)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:394)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:92)
            at org.gradle.api.internal.DefaultCollectionCallbackActionDecorator$BuildOperationEmittingAction.execute(DefaultCollectionCallbackActionDecorator.java:94)
            at org.gradle.internal.ImmutableActionSet$SingletonSet.execute(ImmutableActionSet.java:225)
            at org.gradle.api.internal.DefaultDomainObjectCollection.doAdd(DefaultDomainObjectCollection.java:264)
            at org.gradle.api.internal.DefaultDomainObjectCollection.add(DefaultDomainObjectCollection.java:253)
            at org.gradle.api.internal.DelegatingDomainObjectSet.add(DelegatingDomainObjectSet.java:110)
            at org.gradle.api.internal.artifacts.DefaultDependencySet.add(DefaultDependencySet.java:68)
            at org.gradle.api.internal.artifacts.DefaultDependencySet.add(DefaultDependencySet.java:36)
            at org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.doAdd(DefaultDependencyHandler.java:132)
            at org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.access$100(DefaultDependencyHandler.java:57)
            at org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler$DirectDependencyAdder.add(DefaultDependencyHandler.java:314)
            at org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler$DirectDependencyAdder.add(DefaultDependencyHandler.java:310)
            at org.gradle.api.internal.artifacts.dsl.dependencies.DynamicAddDependencyMethods.tryInvokeMethod(DynamicAddDependencyMethods.java:56)
            at org.gradle.internal.extensibility.MixInClosurePropertiesAsMethodsDynamicObject.tryInvokeMethod(MixInClosurePropertiesAsMethodsDynamicObject.java:34)
            at ch.tutteli.gradle.kotlin.KotlinUtilsPlugin$_augmentProjectExt_closure20$_closure32$_closure35.doCall(KotlinUtilsPlugin.groovy:94)
            at java.base/java.lang.reflect.Method.invoke(Method.java:567)
            at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:323)
            at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1041)
            at groovy.lang.Closure.call(Closure.java:405)
            at groovy.lang.Closure.call(Closure.java:421)
            at org.gradle.util.ClosureBackedAction.execute(ClosureBackedAction.java:71)
            at org.gradle.util.ConfigureUtil.configureTarget(ConfigureUtil.java:154)
            at org.gradle.util.ConfigureUtil.configure(ConfigureUtil.java:105)
            at org.gradle.api.internal.project.DefaultProject.dependencies(DefaultProject.java:1210)
            at java.base/java.lang.reflect.Method.invoke(Method.java:567)
            at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:323)
            at org.gradle.internal.extensibility.MixInClosurePropertiesAsMethodsDynamicObject.tryInvokeMethod(MixInClosurePropertiesAsMethodsDynamicObject.java:34)
            at ch.tutteli.gradle.kotlin.KotlinUtilsPlugin$_augmentProjectExt_closure20$_closure32.doCall(KotlinUtilsPlugin.groovy:92)
            at java.base/java.lang.reflect.Method.invoke(Method.java:567)
            at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:323)
            at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1041)
            at groovy.lang.Closure.call(Closure.java:405)
            at groovy.lang.Closure.call(Closure.java:421)
            at org.gradle.util.ClosureBackedAction.execute(ClosureBackedAction.java:71)
            at org.gradle.util.ConfigureUtil.configureTarget(ConfigureUtil.java:154)
            at org.gradle.util.ConfigureUtil.configure(ConfigureUtil.java:105)
            at org.gradle.api.internal.project.DefaultProject.configure(DefaultProject.java:1187)
            at org.gradle.api.internal.project.DefaultProject.configure(DefaultProject.java:1193)
            at org.gradle.api.Project$configure$4.call(Unknown Source)
            at ch.tutteli.gradle.kotlin.KotlinUtilsPlugin$_augmentProjectExt_closure20.doCall(KotlinUtilsPlugin.groovy:78)
            at ch.tutteli.gradle.kotlin.KotlinUtilsPlugin$_augmentProjectExt_closure20.doCall(KotlinUtilsPlugin.groovy)
            at java.base/java.lang.reflect.Method.invoke(Method.java:567)
            at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:323)
            at org.gradle.internal.extensibility.MixInClosurePropertiesAsMethodsDynamicObject.tryInvokeMethod(MixInClosurePropertiesAsMethodsDynamicObject.java:46)
            at org.gradle.groovy.scripts.BasicScript$ScriptDynamicObject.tryInvokeMethod(BasicScript.java:134)
            at org.gradle.groovy.scripts.BasicScript.invokeMethod(BasicScript.java:83)
            at build_dz731wuzbchvnpulmziuj9sc8.run(/home/josh/Projekte/atrium/build.gradle:178)
            at org.gradle.groovy.scripts.internal.DefaultScriptRunnerFactory$ScriptRunnerImpl.run(DefaultScriptRunnerFactory.java:91)
            at org.gradle.configuration.DefaultScriptPluginFactory$ScriptPluginImpl$2.run(DefaultScriptPluginFactory.java:237)
            at org.gradle.configuration.ProjectScriptTarget.addConfiguration(ProjectScriptTarget.java:77)
            at org.gradle.configuration.DefaultScriptPluginFactory$ScriptPluginImpl.apply(DefaultScriptPluginFactory.java:242)
            at org.gradle.configuration.BuildOperationScriptPlugin$1$1.run(BuildOperationScriptPlugin.java:69)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:402)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:394)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:92)
            at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
            at org.gradle.configuration.BuildOperationScriptPlugin$1.execute(BuildOperationScriptPlugin.java:66)
            at org.gradle.configuration.BuildOperationScriptPlugin$1.execute(BuildOperationScriptPlugin.java:63)
            at org.gradle.configuration.internal.DefaultUserCodeApplicationContext.apply(DefaultUserCodeApplicationContext.java:49)
            at org.gradle.configuration.BuildOperationScriptPlugin.apply(BuildOperationScriptPlugin.java:63)
            at org.gradle.configuration.project.BuildScriptProcessor$1.run(BuildScriptProcessor.java:45)
            at org.gradle.internal.Factories$1.create(Factories.java:26)
            at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.withMutableState(DefaultProjectStateRegistry.java:212)
            at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.withMutableState(DefaultProjectStateRegistry.java:193)
            at org.gradle.configuration.project.BuildScriptProcessor.execute(BuildScriptProcessor.java:42)
            at org.gradle.configuration.project.BuildScriptProcessor.execute(BuildScriptProcessor.java:26)
            at org.gradle.configuration.project.ConfigureActionsProjectEvaluator.evaluate(ConfigureActionsProjectEvaluator.java:35)
            at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject$1.run(LifecycleProjectEvaluator.java:107)
            at org.gradle.internal.Factories$1.create(Factories.java:26)
            at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:189)
            at org.gradle.internal.work.StopShieldingWorkerLeaseService.withLocks(StopShieldingWorkerLeaseService.java:40)
            at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.withProjectLock(DefaultProjectStateRegistry.java:238)
            at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.withMutableState(DefaultProjectStateRegistry.java:232)
            at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.withMutableState(DefaultProjectStateRegistry.java:193)
            at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.run(LifecycleProjectEvaluator.java:96)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:402)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:394)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:92)
            at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
            at org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:68)
            at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:699)
            at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:142)
            at org.gradle.execution.TaskPathProjectEvaluator.configure(TaskPathProjectEvaluator.java:36)
            at org.gradle.execution.TaskPathProjectEvaluator.configureHierarchy(TaskPathProjectEvaluator.java:62)
            at org.gradle.configuration.DefaultProjectsPreparer.prepareProjects(DefaultProjectsPreparer.java:61)
            at org.gradle.configuration.BuildOperatingFiringProjectsPreparer$ConfigureBuild.run(BuildOperatingFiringProjectsPreparer.java:52)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:402)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:394)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:92)
            at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
            at org.gradle.configuration.BuildOperatingFiringProjectsPreparer.prepareProjects(BuildOperatingFiringProjectsPreparer.java:40)
            at org.gradle.initialization.DefaultGradleLauncher.prepareProjects(DefaultGradleLauncher.java:204)
            at org.gradle.initialization.DefaultGradleLauncher.doClassicBuildStages(DefaultGradleLauncher.java:142)
            at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:130)
            at org.gradle.initialization.DefaultGradleLauncher.executeTasks(DefaultGradleLauncher.java:110)
            at org.gradle.internal.invocation.GradleBuildController$1.execute(GradleBuildController.java:60)
            at org.gradle.internal.invocation.GradleBuildController$1.execute(GradleBuildController.java:57)
            at org.gradle.internal.invocation.GradleBuildController$3.create(GradleBuildController.java:85)
            at org.gradle.internal.invocation.GradleBuildController$3.create(GradleBuildController.java:78)
            at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:189)
            at org.gradle.internal.work.StopShieldingWorkerLeaseService.withLocks(StopShieldingWorkerLeaseService.java:40)
            at org.gradle.internal.invocation.GradleBuildController.doBuild(GradleBuildController.java:78)
            at org.gradle.internal.invocation.GradleBuildController.run(GradleBuildController.java:57)
            at org.gradle.tooling.internal.provider.ExecuteBuildActionRunner.run(ExecuteBuildActionRunner.java:31)
            at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
            at org.gradle.launcher.exec.BuildOutcomeReportingBuildActionRunner.run(BuildOutcomeReportingBuildActionRunner.java:63)
            at org.gradle.tooling.internal.provider.ValidatingBuildActionRunner.run(ValidatingBuildActionRunner.java:32)
            at org.gradle.launcher.exec.BuildCompletionNotifyingBuildActionRunner.run(BuildCompletionNotifyingBuildActionRunner.java:39)
            at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:51)
            at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:45)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:416)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:406)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:102)
            at org.gradle.internal.operations.DelegatingBuildOperationExecutor.call(DelegatingBuildOperationExecutor.java:36)
            at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner.run(RunAsBuildOperationBuildActionRunner.java:45)
            at org.gradle.launcher.exec.InProcessBuildActionExecuter$1.transform(InProcessBuildActionExecuter.java:50)
            at org.gradle.launcher.exec.InProcessBuildActionExecuter$1.transform(InProcessBuildActionExecuter.java:47)
            at org.gradle.composite.internal.DefaultRootBuildState.run(DefaultRootBuildState.java:78)
            at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:47)
            at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:31)
            at org.gradle.launcher.exec.BuildTreeScopeBuildActionExecuter.execute(BuildTreeScopeBuildActionExecuter.java:42)
            at org.gradle.launcher.exec.BuildTreeScopeBuildActionExecuter.execute(BuildTreeScopeBuildActionExecuter.java:28)
            at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:78)
            at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:52)
            at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:59)
            at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:36)
            at org.gradle.tooling.internal.provider.SessionScopeBuildActionExecuter.execute(SessionScopeBuildActionExecuter.java:68)
            at org.gradle.tooling.internal.provider.SessionScopeBuildActionExecuter.execute(SessionScopeBuildActionExecuter.java:38)
            at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:37)
            at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:26)
            at org.gradle.tooling.internal.provider.ParallelismConfigurationBuildActionExecuter.execute(ParallelismConfigurationBuildActionExecuter.java:43)
            at org.gradle.tooling.internal.provider.ParallelismConfigurationBuildActionExecuter.execute(ParallelismConfigurationBuildActionExecuter.java:29)
            at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:60)
            at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:32)
            at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:55)
            at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:41)
            at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:48)
            at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:32)
            at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:68)
            at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
            at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
            at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:39)
            at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
            at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:27)
            at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
            at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:35)
            at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
            at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:78)
            at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:75)
            at org.gradle.util.Swapper.swap(Swapper.java:38)
            at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:75)
            at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
            at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55)
            at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
            at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:63)
            at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
            at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
            at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:82)
            at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
            at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
            at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:52)
            at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:297)
            at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
            at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
            at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
            at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
            at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
            at java.base/java.lang.Thread.run(Thread.java:835)
    

    closes #217


    I confirm that I have read the Contributor Agreements v1.0, agree to be bound on them and confirm that my contribution is compliant.

    opened by jGleitz 23
  • Path.hasSameTextualContentAs

    Path.hasSameTextualContentAs

    Platform (jvm, js, android): jvm Extension (none, kotlin 1.3, jdk8): jdk8

    Code related feature

    expect(path).hasSameTextualContentAs(otherPath)
    

    per default UTF-8 should be used when reading both files. However, one shall be able to overwrite the default, something like:

    expect(path).hasSameContentAs(otherPath, sourceCharset=Charsets.ISO_8859_1, targetCharset=Charsets.UTF_16)
    

    Bonus add also hasSameBinaryContentAs

    help wanted 
    opened by robstoll 21
  • add asLocalDateTime to ava.util.Date

    add asLocalDateTime to ava.util.Date

    Platform (all, jvm, js): jvm Extension (none, kotlin 1.3): none

    Code related feature

    expect(Date()).asLocalDateTime().toBeAfter(...)
    
    //instead of
    
    expect(Date().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()).toBeAfter(...);
    

    Following the things you need to do:

    api-fluent

    • [ ] provide a val which returns Expect<LocalDateTime> in dateSubjectChangers.kt (see fileSubjectChangers.kt as a guideline)
    • [ ] provide a fun which expects an assertionCreator-lambda and returns Expect<Date> in dateSubjectChangers.kt (see fileSubjectChangers.kt as a guideline)
    • [ ] add @since 0.20.0 (adapt to current milestone) to KDOC
    • [ ] extend or write a separate Spec named DateAsLocalDateTimeExpectationsSpec in specs -> jvmMain (see for instance FileAsPathExpectationsSpec) and extend it in atrium-api-fluent-en_GB -> jvmTest

    api-infix

    • [ ] provide a val which returns Expect<Date> in dateSubjectChangers.kt(see fileSubjectChangers.kt as a guideline)
    • [ ] provide a fun which expects an assertionCreator-lambda and returns Expect<AB> in dateSubjectChangers.kt (see fileSubjectChangers.kt as a guideline)
    • [ ] add @since 0.20.0 (adapt to current milestone) to KDOC
    • [ ] extend or write a separate Spec named DateAsLocalDateTimeExpectationsSpec in specs -> commonMain (see for instance FileAsPathExpectationsSpec) and extend it in atrium-api-infix-en_GB -> jvmTest

    Your first contribution?

    • Write a comment I'll work on this if you would like to take this issue over. This way we get the chance to revise the description in case things have changed in the meantime, we might give you additional hints and we can assign the task to you, so that others do not start as well.
    • See Your first code contribution for guidelines.
    • Do not hesitate to ask questions here or to contact us via Atrium's slack channel if you need help (Invite yourself in case you do not have an account yet).
    help wanted good first issue 
    opened by robstoll 0
  • add asLocalDate for java.util.Date

    add asLocalDate for java.util.Date

    Platform (all, jvm, js): jvm Extension (none, kotlin 1.3): none

    Code related feature

    expect(Date()).asLocalDate().toBeAfter(...)
    
    //instead of
    
    expect(Date().toInstant().atZone(ZoneId.systemDefault()).toLocalDate()).toBeAfter(...);
    

    Following the things you need to do:

    api-fluent

    • [ ] provide a val which returns Expect<LocalDate> in dateSubjectChangers.kt (see fileSubjectChangers.kt as a guideline)
    • [ ] provide a fun which expects an assertionCreator-lambda and returns Expect<Date> in dateSubjectChangers.kt (see fileSubjectChangers.kt as a guideline)
    • [ ] add @since 0.20.0 (adapt to current milestone) to KDOC
    • [ ] extend or write a separate Spec named DateAsLocalDateExpectationsSpec in specs -> jvmMain (see for instance FileAsPathExpectationsSpec) and extend it in atrium-api-fluent-en_GB -> jvmTest

    api-infix

    • [ ] provide a val which returns Expect<Date> in dateSubjectChangers.kt(see fileSubjectChangers.kt as a guideline)
    • [ ] provide a fun which expects an assertionCreator-lambda and returns Expect<AB> in dateSubjectChangers.kt (see fileSubjectChangers.kt as a guideline)
    • [ ] add @since 0.20.0 (adapt to current milestone) to KDOC
    • [ ] extend or write a separate Spec named DateAsLocalDateExpectationsSpec in specs -> commonMain (see for instance FileAsPathExpectationsSpec) and extend it in atrium-api-infix-en_GB -> jvmTest

    Your first contribution?

    • Write a comment I'll work on this if you would like to take this issue over. This way we get the chance to revise the description in case things have changed in the meantime, we might give you additional hints and we can assign the task to you, so that others do not start as well.
    • See Your first code contribution for guidelines.
    • Do not hesitate to ask questions here or to contact us via Atrium's slack channel if you need help (Invite yourself in case you do not have an account yet).
    help wanted good first issue 
    opened by robstoll 0
  • Kt 54674 -> pull request for discussing if statement check

    Kt 54674 -> pull request for discussing if statement check

    Pull request on this branch because it's what I'm working with so far


    I confirm that I have read the Contributor Agreements v1.0, agree to be bound on them and confirm that my contribution is compliant.

    opened by JordanllHarper 0
  • Kotlin bug Result unwrapping affects Atrium

    Kotlin bug Result unwrapping affects Atrium

    Affected Version

    0.18.0

    API

    fluent-en_GB, infix-en_GB

    Platform

    jvm

    Kotlin Version

    1.4

    How to reproduce the problem?

    expect(Result.failure<int>(IllegalArgumentException("oh no")).toBeAFailure()
    

    => is success

    Describe the bug

    See https://youtrack.jetbrains.com/issue/KT-50974 => fix is only provided in Kotlin 1.6.20

    Expected behaviour

    We want that people working with Kotlin 1.4.x and 1.5.x are not affected by this Kotlin bug. Thus, we need to implement a workaround so that the user does not get in touch with this bug (at least when using Atrium).

    We can do this by comparing container.maybeSubject with the (wrong) subject provided by Kotlin. In case they differ and it is a Result.isSuccess which contains another Result, then we unwrap it.

    bug help wanted 
    opened by robstoll 6
  • Feature/add specs maptocontain

    Feature/add specs maptocontain

    Outline

    Add some specs to MapToContainInOrderOnlyKeyValueExpectationsSpec .

    I made those reference to IterableToContainInOrderOnlyValuesExpectationsSpec .

    Issue

    #1129


    I confirm that I have read the Contributor Agreements v1.0, agree to be bound on them and confirm that my contribution is compliant.

    opened by simonNozaki 0
  • add tests for reportOptions to MapToContainInOrderOnlyKeyValueExpectationsSpec

    add tests for reportOptions to MapToContainInOrderOnlyKeyValueExpectationsSpec

    Platform (all, jvm, js): all Extension (none, kotlin 1.3): none

    Code related feature

    Following the things you need to do:

    • [ ] see TODOs in MapToContainInOrderOnlyKeyValueExpectationsSpec

    Your first contribution?

    • Write a comment I'll work on this if you would like to take this issue over. This way we get the chance to revise the description in case things have changed in the meantime, we might give you additional hints and we can assign the task to you, so that others do not start as well.
    • See Your first code contribution for guidelines.
    • Do not hesitate to ask questions here or to contact us via Atrium's slack channel if you need help (Invite yourself in case you do not have an account yet).
    help wanted good first issue 
    opened by robstoll 10
Releases(v0.18.0)
  • v0.18.0(Apr 25, 2022)

    Table of Content

    New Features

    All APIs

    • aligned reporting to new to + infinitive schema
    • start error message with I expected... #1090
    • also add reportOptions to inAnyOrder.only #1094

    api-fluent-en_GB

    • no fluent only additions this time

    api-infx-en_GB

    • add report option to inGiven order and only in api-infix #1050

    Logic / Core

    • not a feature as such but more a recommendation. We started to split API files into XyExpectations.kt, XyFeatureExtractor.kt and XySubjectChangers.kt -- we think it will help to get the distinction between those three categories
    • add createAndAppend to AssertionContainer expecting Translatable #1061

    Fixes

    • additional elements detected wrong for inAnyOrder.only #1103

    Improvements

    • include given type in error message of IterableLikeToIterableTransformer #1057
    • add samples for optionalExpectations of api-fluent #917 => thanks to @timacosta
    • add samples for featureAssertions of api-fluent #648 => thanks to @iljakorneckis
    • add samples for Map.size to api-fluent and api-infix #1039 => thanks to @ashwini-desai
    • add samples for fun0Expectations also to api-infix #1044 => thanks to @Kushiro-C
    • generate jdk11 byte code instead of jdk1.6 (see breaking changes below for more details)

    Deprecation

    • ErrorMessages enum entries which contained ASSERTION => use the replacement with EXPECTATION
    • all Description...Assertion.kt => use use the replacement from Description...Expectation.kt
    • DescriptionBasic.IS => use TO_BE instead
    • DescriptionBasic.IS_NOT => use NOT_TO_BE instead
    • maplike.contains.checkers.* => looks like they were introduced by accident, they will be removed with 0.19.0 without replacement
    • COULD_NOT_EVALUATE_DEFINED_ASSERTIONS => will be removed without replacement
    • VISIT_COULD_NOT_EVALUATE_ASSERTIONS => will be removed without replacement
    • InOrderOnlyReportingOptions.showOnlyFailingIfMoreElementsThan => use showOnlyFailingIfMoreElementsThan will be removed with 0.19.0
    • InOrderOnlyReportingOptions.numberOfElementsInSummary => use maxNumberOfExpectedElementsForSummary will be removed with 0.19.0

    Deprecations with 0.19.0

    • we will switch to the new MPP plugin, could be we have to deprecate a few things in this context

    Deprecations with 0.20.0

    We will make a major refactoring on core and logic level. Those changes should not affect most Atrium users. It will affect you if you:

    • created own expectation function based on assertionBuilder or other types which are defined in core or logic.
    • use an own expectation verb

    Migration steps/pointers will be provided in the release notes as usual.

    The following changes are planned:

    • replace Assertion with Proof and along with it rename many types incorporating Assertion in its name or in its package's name => we will remove Assertion and co. with 0.22.0 at the latest)
    • re-write reporting entirely, a lot of types in ch.tutteli.atrium.reporting will be affected (could be we move this to 0.21.0)

    Breaking Changes

    Planned (previously deprecated or announced)

    • removed the pre-defined assertion verbs assert and assertThat as they do not fit the to + infinitive naming schema => switch to expect
    • moved expectation functions already following the to + infinitive convention from ...Assertions.kt to the corresponding ...Expectations.kt file which is a binary breaking change => please re-compile your sources
    • renamed files named charSequence/iterableLike/mapLikeContains... to charSequence/iterableLike/mapLikeToContain which is a binary breaking change => please re-compile your sources
    • we will generate jdk 11 byte code (yes, we skip jdk8 as newer android versions support jdk11 byte code) an no longer jdk1.6 (and thus also rely on Kotlin's stlib-jdk8) => might affect you in case you still generate jdk1.6 or jdk1.8 in tests as inlining will not work (jdk11 code cannot be inlined into jdk1.6 or jdk1.8)

    Unplanned

    • added InAnyOrderOnlyReportingOptions to inAnyOrder.only => please re-compile
    • added InOrderOnlyReportingOptions and InAnyOrderOnlyReportingOptions to inOrder.only.grouped => please re-compile
    • InOrderOnlyMatcher.addSingleEntryAssertion now expects a DescriptionIterableLikeExpectation instead of a DescriptionIterableAssertion => we guess this should not affect 99.999% of the users :laughing:
    • renamed WithInOrderOnlyReportingOptions.report to WithInOrderOnlyReportingOptions.options

    Breaking Changes with 0.19.0

    First of all, see deprecation above which will be removed with 0.19.0, some of them are breaking as well

    • we drop support for jdk 14 (i.e. we no longer build against it, most likely it still works without problems)
    • might be that we need to change package names which is a binary and source backward compatibility break in case we switch to the new Kotlin MPP plugin with 0.19.0

    Breaking Changes with 0.20.0

    • BulletPointProvider will most likely use a BulletPointIdentifier from a different package

    Breaking Changes with 0.21.0

    • not pre-seen for now

    Breaking Changes with 0.22.0

    • we will remove all deprecated...Assertions.kt files in all APIs
    • we will remove all deprecated... files in the translation modules
    • we will remove all deprecated stuff on the logic/core level

    Breaking Changes with 1.0.0

    See atrium-roadmap -> Milestone 1.0.0

    Migrating deprecated functionality

    Use the ReplaceWith in the corresponding @Deprecated annotations.

    Please have a look at older release-notes in case you don't migrate from 0.17.0

    Sponsors

    We would like to thank   Tegonal GmbH (Tegonal GmbH, Bern, Switzerland) for sponsoring the time @robstoll is working on Atrium.

    Are you using Atrium at work?

    Please consider to support the project as well by:

    • sponsoring robstoll (Author and main contributor)
    • share your expectation functions with others
    • report bugs
    • provide feedback in case you miss a feature
    Source code(tar.gz)
    Source code(zip)
  • v0.17.0(Oct 26, 2021)

    Table of Content

    New Features

    All APIs

    • Refactor/Renaming to a consistent to + infitive schema #840
    • notToBeGreaterThan and notToBeLessThan #878 => thanks to @jGleitz for the feature request
    • add Path.notToBeWritable #1001 => thanks to @rhushikesh for the implementation
    • add Path.notToBeExecutable #1015 => thanks to @rhushikesh for the implementation
    • add Path.notToBeReadable #942 => thanks to @botex98 for the implementation

    api-fluent-en_GB

    • Add option to display only unexpected or missing elements in asserts on collections #292

    api-infx-en_GB

    • no infix only additions this time

    Logic / Core

    • none this time

    Fixes

    • fix empty feature extractor #950

    Improvements

    • Move hasNext() check out of Iterable.all #305 => thanks to @wordhou for the implementation
    • improve reporting for containsDuplicates #813 => thanks to @wordhou for the implementation
    • less verbose containsNot reporting and more hints #722 => thanks to @wordhou for the implementation
    • don't show number of occurrences for contains.inAnyOrder.atLeast(1) #310 => thanks to @wordhou for the implementation
    • rename withFailureHint to withHelpOnFailure #671 => thanks to @stevenlmcgraw for the implementation
    • show more than first line when using mocha test runner #691 => thanks to @wordhou for the implementation
    • Empty assertionCreator lambda warning is added twice #933 => thanks to @wordhou for the implementation
    • rewrite samples to use Kotlin DSL for gradle #873 => thanks to @wordhou
    • add samples for iterableExpectations of api-fluent #651 => thanks to @wordhou
    • add samples for fun0Assertions of api-fluent #650 => thanks to @szatyinadam
    • add samples for mapExpectations of api-infix #861 => thanks to @kacmacuna
    • add samples for mapEntryExpectations of api-infix #862 => thanks to @kacmacuna
    • add samples for iterableExpectations of api-infix #992 => thanks to @vinayak03
    • add samples for fileAssertions of api-fluent #994 => thanks to @iljakorneckis
    • add samples for resultExpectations of api-fluent #993 => thanks to @vinayak03
    • add samples for bigDecimalExpectations of api-fluent #904 => thanks to @timacosta
    • add samples for pathExpectations of api-fluent #919 => thanks to @kacmacuna
    • add samples for throwableExpectations of api-fluent #658 => thanks to @szatyinadam
    • add samples for infix throwable expectations #1002 => thanks to @szatyinadam
    • add samples for chronoLocalDateExpectations of api-fluent #914 => thanks to @dias-wagner
    • add samples for chronoLocalDateTimeExpectations of api-fluent #915 => thanks to @dias-wagner
    • add samples for chronoZonedDateTimeExpectations of api-fluent #916 => thanks to @dias-wagner
    • add samples for localDateAssertions of api-fluent and api-infix #996 => thanks to @rhushikesh
    • add samples for localDateTimeAssertions of api-fluent and api-infix #997 => thanks to @rhushikesh
    • add samples for zonedDateTimeAssertions of api-fluent #998 => thanks to @vinayak03
    • add samples for arrayAssertions of api-infix #675 => thanks to @szatyinadam
    • add samples for pairAssertions of api-infix #679 => thanks to @szatyinadam
    • add samples for pathExpectations of api-infix #999 => thanks to @rhushikesh
    • add samples for sequenceExpectations of api-fluent #657 => thanks to @loge1998
    • add samples for ZonedDateTime to api-infix #1019 => thanks to @iljakorneckis
    • Add ChronoLocalDate, ChronoLocalDateTime and ChronoZonedDateTime expectation samples for the infix api #941 => thanks to @dias-wagner

    Deprecation

    • we deprecated the pre-defined assertion verbs assert and assertThat as they do not fit the to + infinitive naming schema; they will be removed with 0.18.0 => switch to expect
    • basically all expectation functions which did not follow the to + infinitive naming schema => see migration further below

    Deprecations with 0.18.0

    • we will deprecate all translation enum entries which do not follow the to + infinitive naming schema

    Deprecations with 0.19.0

    We will make a major refactoring on core and logic level where we will:

    • replace Assertion with Proof and along with it rename many types incorporating Assertion in its name or in its package's name => we will remove Assertion and co. with 0.21.0 at the latest)
    • re-write reporting entirely, a lot of types in ch.tutteli.atrium.reporting will be affected (could be we move this to 0.20.0)

    Those changes should not affect most Atrium users. It will affect you if you:

    • created own assertion function based on assertionBuilder or other types which are defined in core or logic.
    • use an own expectation verb

    Migration steps/pointers will be provided in the release notes as usual.

    Breaking Changes

    Planned (previously deprecated or announced)

    • removed deprecated modules: atrium-core-robstoll, atrium-core-robstoll-lib, atrium-domain-builders (including ExpectBuilder) -- see https://github.com/robstoll/atrium/pull/856
    • removed deprecated functions/types which were scheduled to be removed with 0.17.0, i.a. ReporterFactory, RawString, collectForDifferentSubject -- see https://github.com/robstoll/atrium/pull/858 and https://github.com/robstoll/atrium/pull/859
    • not really a breaking change but just as info, we renamed core-api to core (as we no longer split core into api and implementation)

    Unplanned

    • none this time

    Breaking Changes with 0.18.0

    First of all, see deprecation above which will be removed with 0.18.0, some of them are breaking as well

    • we will move expectation functions already following the to + infinitive convention from ...Assertions.kt to the corresponding ...Expectations.kt file which is a binary breaking change => please re-compile your sources
    • we will rename files named charSequence/iterableLike/mapLikeContains... to charSequence/iterableLike/mapLikeToContain which is a binary breaking change => please re-compile your sources
    • we will generate jdk 11 byte code (yes, we skip jdk8 as newer android versions support jdk11 byte code) an no longer jdk1.6 (and thus also rely on Kotlin's stlib-jdk11) => might affect you in case you still generate jdk1.6 or jdk1.8 in tests as inlining will not work (jdk11 code cannot be inlined into jdk1.6 or jdk1.8)

    Breaking Changes with 0.19.0

    • BulletPointProvider will most likely use a BulletPointIdentifier from a different package
    • might be that we need to change package names which is a binary and source backward compatibility break in case we switch to the new Kotlin MPP plugin with 0.19.0

    Breaking Changes with 0.20.0

    • we will remove all deprecated ...Assertions as well as other deprecating stuff on the logic level.

    Breaking Changes with 1.0.0

    See atrium-roadmap -> Milestone 1.0.0

    Migrating deprecated functionality

    The following command is carrying out some search & replace so that you don't need to rely on the (unfortunately sometimes buggy) behaviour of ReplaceWith.

    find . -type f -name "*.kt" -a \( -path "*/src/test/*" -o -path "*/src/*Test/*" \) | xargs perl -0777 -i \
    -pe 's/import ch.tutteli.atrium.api.verbs.(assert|assertThat)\n/import ch.tutteli.atrium.api.verbs.expect\n/g;' \
    -pe 's/(assert|assertThat)(\(| ?\{)/expect$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.toBe\n/import ch.tutteli.atrium.api.$1.en_GB.toEqual\n/g;' \
    -pe 's/([\. ])toBe([\( ])/$1toEqual$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.notToBe\n/import ch.tutteli.atrium.api.$1.en_GB.notToEqual\n/g;' \
    -pe 's/([\. ])notToBe([\( ])/$1notToEqual$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isSameAs\n/import ch.tutteli.atrium.api.$1.en_GB.toBeTheInstance\n/g;' \
    -pe 's/([\. ])isSameAs([\( ])/$1toBeTheInstance$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isNotSameAs\n/import ch.tutteli.atrium.api.$1.en_GB.notToBeTheInstance\n/g;' \
    -pe 's/([\. ])isNotSameAs([\( ])/$1notToBeTheInstance$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.notToBeNull\n/import ch.tutteli.atrium.api.$1.en_GB.notToEqualNull\n/g;' \
    -pe 's/([\. ])notToBeNull([\( ])/$1notToEqualNull$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isA\n/import ch.tutteli.atrium.api.$1.en_GB.toBeAnInstanceOf\n/g;' \
    -pe 's/([\. ])isA</$toBeAnInstanceOf</g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isNoneOf\n/import ch.tutteli.atrium.api.$1.en_GB.notToBeOneOf\n/g;' \
    -pe 's/([\. ])isNoneOf([\( ])/$1notToBeOneOf$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isNotIn\n/import ch.tutteli.atrium.api.$1.en_GB.notToBeIn\n/g;' \
    -pe 's/([\. ])isNotIn([\( ])/$1notToBeIn$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.contains\n/import ch.tutteli.atrium.api.$1.en_GB.toContain\n/g;' \
    -pe 's/([\. ])contains([\( \.])/$1toContain$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.containsNot\n/import ch.tutteli.atrium.api.$1.en_GB.notToContain\n/g;' \
    -pe 's/([\. ])containsNot([\( \.])/$1notToContain$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.containsRegex\n/import ch.tutteli.atrium.api.$1.en_GB.toContainRegex\n/g;' \
    -pe 's/([\. ])containsRegex([\( ])/$1toContainRegex$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.contains\n/import ch.tutteli.atrium.api.$1.en_GB.toContain\n/g;' \
    -pe 's/([\. ])contains([\( ])/$1toContain$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.startsWith\n/import ch.tutteli.atrium.api.$1.en_GB.toStartWith\n/g;' \
    -pe 's/([\. ])startsWith([\( ])/$1toStartWith$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.startsNotWith\n/import ch.tutteli.atrium.api.$1.en_GB.notToStartWith\n/g;' \
    -pe 's/([\. ])startsNotWith([\( ])/$1notToStartWith$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.endsWith\n/import ch.tutteli.atrium.api.$1.en_GB.toEndWith\n/g;' \
    -pe 's/([\. ])endsWith([\( ])/$1toEndWith$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.endsNotWith\n/import ch.tutteli.atrium.api.$1.en_GB.notToEndWith\n/g;' \
    -pe 's/([\. ])endsNotWith([\( ])/$1notToEndWith$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.fluent.en_GB.isEmpty\n/import ch.tutteli.atrium.api.fluent.en_GB.toBeEmpty\n/g;' \
    -pe 's/([\. ])isEmpty\(/$1toBeEmpty\(/g;' \
    -pe 's/import ch.tutteli.atrium.api.fluent.en_GB.isNotEmpty\n/import ch.tutteli.atrium.api.fluent.en_GB.notToBeEmpty\n/g;' \
    -pe 's/([\. ])isNotEmpty\(/$1notToBeEmpty\(/g;' \
    -pe 's/import ch.tutteli.atrium.api.fluent.en_GB.isNotBlank\n/import ch.tutteli.atrium.api.fluent.en_GB.notToBeBlank\n/g;' \
    -pe 's/([\. ])isNotBlank\(/notToBeBlank\(/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.matches\n/import ch.tutteli.atrium.api.$1.en_GB.toMatch\n/g;' \
    -pe 's/([\. ])matches([\( ])/$1toMatch$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.mismatches\n/import ch.tutteli.atrium.api.$1.en_GB.notToMatch\n/g;' \
    -pe 's/([\. ])mismatches([\( ])/$1notToMatch$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.hasSize\n/import ch.tutteli.atrium.api.$1.en_GB.toHaveSize\n/g;' \
    -pe 's/([\. ])hasSize([\( ])/$1toHaveSize$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isLessThan\n/import ch.tutteli.atrium.api.$1.en_GB.toBeLessThan\n/g;' \
    -pe 's/([\. ])isLessThan([\( ])/$1toBeLessThan$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isLessThanOrEqual\n/import ch.tutteli.atrium.api.$1.en_GB.toBeLessThanOrEqualTo\n/g;' \
    -pe 's/([\. ])isLessThanOrEqual([\( ])/$1toBeLessThanOrEqualTo$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isGreaterThan\n/import ch.tutteli.atrium.api.$1.en_GB.toBeGreaterThan\n/g;' \
    -pe 's/([\. ])isGreaterThan([\( ])/$1toBeGreaterThan$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isGreaterThanOrEqualTo\n/import ch.tutteli.atrium.api.$1.en_GB.toBeGreaterThanOrEqualTo\n/g;' \
    -pe 's/([\. ])isGreaterThanOrEqual([\( ])/$1toBeGreaterThanOrEqual$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isEqualComparingTo\n/import ch.tutteli.atrium.api.$1.en_GB.toBeEqualComparingTo\n/g;' \
    -pe 's/([\. ])isEqualComparingTo([\( ])/$1toBeEqualComparingTo$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.toBeWithErrorTolerance\n/import ch.tutteli.atrium.api.$1.en_GB.toEqualWithErrorTolerance\n/g;' \
    -pe 's/([\. ])toBeWithErrorTolerance([\( ])/$1toEqualWithErrorTolerance$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.containsExactly\n/import ch.tutteli.atrium.api.$1.en_GB.toContainExactly\n/g;' \
    -pe 's/([\. ])containsExactly([\( ])/$1toContainExactly$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.containsExactlyElementsOf\n/import ch.tutteli.atrium.api.$1.en_GB.toContainExactlyElementsOf\n/g;' \
    -pe 's/([\. ])containsExactlyElementsOf([\( ])/$1toContainExactlyElementsOf$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.containsElementsOf\n/import ch.tutteli.atrium.api.$1.en_GB.toContainElementsOf\n/g;' \
    -pe 's/([\. ])containsElementsOf([\( ])/$1toContainElementsOf$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.any\n/import ch.tutteli.atrium.api.$1.en_GB.toHaveElementsAndAny\n/g;' \
    -pe 's/([\. ])any([\( ])/$1toHaveElementsAndAny$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.none\n/import ch.tutteli.atrium.api.$1.en_GB.toHaveElementsAndNone\n/g;' \
    -pe 's/([\. ])none([\( ])/$1toHaveElementsAndNone$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.all\n/import ch.tutteli.atrium.api.$1.en_GB.toHaveElementsAndAll\n/g;' \
    -pe 's/([\. ])all([\( ])/$1toHaveElementsAndAll$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.all\n/import ch.tutteli.atrium.api.$1.en_GB.toHaveElementsAndAll\n/g;' \
    -pe 's/([\. ])all([\( ])/$1toHaveElementsAndAll$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.containsNoDuplicates\n/import ch.tutteli.atrium.api.$1.en_GB.toHaveElementsAndNoDuplicates\n/g;' \
    -pe 's/([\. ])containsNoDuplicates([\( ])/$toHaveElementsAndNoDuplicates$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.containsOnly\n/import ch.tutteli.atrium.api.$1.en_GB.toContainOnly\n/g;' \
    -pe 's/([\. ])containsOnly([\( ])/$1toContainOnly$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.containsEntriesOf\n/import ch.tutteli.atrium.api.$1.en_GB.toContainEntriesOf\n/g;' \
    -pe 's/([\. ])containsEntriesOf([\( ])/$1toContainEntriesOf$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.containsOnlyEntriesOf\n/import ch.tutteli.atrium.api.$1.en_GB.toContainOnlyEntriesOf\n/g;' \
    -pe 's/([\. ])containsOnlyEntriesOf([\( ])/$1toContainOnlyEntriesOf$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.containsKey\n/import ch.tutteli.atrium.api.$1.en_GB.toContainKey\n/g;' \
    -pe 's/([\. ])containsKey([\( ])/$1toContainKey$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.containsNotKey\n/import ch.tutteli.atrium.api.$1.en_GB.notToContainKey\n/g;' \
    -pe 's/([\. ])containsNotKey([\( ])/$1notToContainKey$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isKeyValue\n/import ch.tutteli.atrium.api.$1.en_GB.toEqualKeyValue\n/g;' \
    -pe 's/([\. ])isKeyValue([\( ])/$1toEqualKeyValue$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.messageContains\n/import ch.tutteli.atrium.api.$1.en_GB.messageToContain\n/g;' \
    -pe 's/([\. ])messageContains([\( ])/$1messageToContain$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isNumericallyEqualTo\n/import ch.tutteli.atrium.api.$1.en_GB.toEqualNumerically\n/g;' \
    -pe 's/([\. ])isNumericallyEqualTo([\( ])/$1toEqualNumerically$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isNotNumericallyEqualTo\n/import ch.tutteli.atrium.api.$1.en_GB.notToEqualNumerically\n/g;' \
    -pe 's/([\. ])isNotNumericallyEqualTo([\( ])/$1notToEqualNumerically$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isEqualIncludingScale\n/import ch.tutteli.atrium.api.$1.en_GB.toEqualIncludingScale\n/g;' \
    -pe 's/([\. ])isEqualIncludingScale([\( ])/$1toEqualIncludingScale$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isNotEqualIncludingScale\n/import ch.tutteli.atrium.api.$1.en_GB.notToEqualIncludingScale\n/g;' \
    -pe 's/([\. ])isNotEqualIncludingScale([\( ])/$1notToEqualIncludingScale$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isBefore\n/import ch.tutteli.atrium.api.$1.en_GB.toBeBefore\n/g;' \
    -pe 's/([\. ])isBefore([\( ])/$1toBeBefore$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isBeforeOrEqual\n/import ch.tutteli.atrium.api.$1.en_GB.toBeBeforeOrTheSamePointInTimeAs\n/g;' \
    -pe 's/([\. ])isBeforeOrEqual([\( ])/$1toBeBeforeOrTheSamePointInTimeAs$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isAfter\n/import ch.tutteli.atrium.api.$1.en_GB.toBeAfter\n/g;' \
    -pe 's/([\. ])isAfter([\( ])/$1toBeAfter$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isAfterOrEqual\n/import ch.tutteli.atrium.api.$1.en_GB.toBeAfterOrTheSamePointInTimeAs\n/g;' \
    -pe 's/([\. ])isAfterOrEqual([\( ])/$1toBeAfterOrTheSamePointInTimeAs$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isEqual\n/import ch.tutteli.atrium.api.$1.en_GB.toBeTheSamePointInTimeAs\n/g;' \
    -pe 's/([\. ])isEqual([\( ])/$1toBeTheSamePointInTimeAs$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.fluent.en_GB.isPresent\n/import ch.tutteli.atrium.api.fluent.en_GB.toBePresent\n/g;' \
    -pe 's/([\. ])isPresent\(/$1toBePresent\(/g;' \
    -pe 's/import ch.tutteli.atrium.api.fluent.en_GB.isSuccess\n/import ch.tutteli.atrium.api.fluent.en_GB.toBeASuccess\n/g;' \
    -pe 's/([\. ])isSuccess\(/$1toBeASuccess\(/g;' \
    -pe 's/import ch.tutteli.atrium.api.infix.en_GB.success\n/import ch.tutteli.atrium.api.infix.en_GB.aSuccess\n/g;' \
    -pe 's/toBe\(success\)/toBe\(aSuccess\)/g;' \
    -pe 's/toBe success \{/toBe aSuccess \{/g;' \
    -pe 's/import ch.tutteli.atrium.api.(fluent|infix).en_GB.isFailure\n/import ch.tutteli.atrium.api.$1.en_GB.toBeAFailure\n/g;' \
    -pe 's/([\. ])isFailure([\( ])/$1toBeAFailure$2/g;' \
    -pe 's/import ch.tutteli.atrium.api.fluent.en_GB.exists\n/import ch.tutteli.atrium.api.fluent.en_GB.toExist\n/g;' \
    -pe 's/([\. ])exists\(/$1toExist\(/g;' \
    -pe 's/import ch.tutteli.atrium.api.fluent.en_GB.existsNot\n/import ch.tutteli.atrium.api.fluent.en_GB.notToExist\n/g;' \
    -pe 's/([\. ])existsNot\(/$1notToExist\(/g;' \
    -pe 's/import ch.tutteli.atrium.api.fluent.en_GB.is(Readable|Writable|Executable|Absolute|Relative)\n/import ch.tutteli.atrium.api.fluent.en_GB.toBe$1\n/g;' \
    -pe 's/([\. ])is(Readable|Writable|Executable|Absolute|Relative)\(/$1toBe$2\(/g;' \
    -pe 's/import ch.tutteli.atrium.api.fluent.en_GB.is(RegularFile|Directory|SymbolicLink)\n/import ch.tutteli.atrium.api.fluent.en_GB.toBeA$1\n/g;' \
    -pe 's/([\. ])is(RegularFile|Directory|SymbolicLink)\(/$1toBeA$2\(/g;' \
    -pe 's/import ch.tutteli.atrium.api.fluent.en_GB.isEmptyDirectory\n/import ch.tutteli.atrium.api.fluent.en_GB.toBeAnEmptyDirectory\n/g;' \
    -pe 's/([\. ])isEmptyDirectory\(/$1toBeAnEmptyDirectory\(/g;' \
    -pe 's/import ch.tutteli.atrium.api.fluent.en_GB.hasDirectoryEntry\n/import ch.tutteli.atrium.api.fluent.en_GB.toHaveTheDirectoryEntries\n/g;' \
    -pe 's/([\. ])hasDirectoryEntry\(/$1toHaveTheDirectoryEntries\(/g;' \
    -pe 's/import ch.tutteli.atrium.api.fluent.en_GB.hasSameTextualContentAs\n/import ch.tutteli.atrium.api.fluent.en_GB.toHaveTheSameTextualContentAs\n/g;' \
    -pe 's/([\. ])hasSameTextualContentAs\(/$1toHaveTheSameTextualContentAs\(/g;' \
    -pe 's/import ch.tutteli.atrium.api.fluent.en_GB.hasSameBinaryContentAs\n/import ch.tutteli.atrium.api.fluent.en_GB.toHaveTheSameBinaryContentAs\n/g;' \
    -pe 's/([\. ])hasSameBinaryContentAs\(/$1toHaveTheSameBinaryContentAs\(/g;'
    

    It might well be that the above commands replace a bit too much in certain cirumstances (e.g. if you use Kotlin's all, any or none in tests or startsWith, endsWith etc.) - we guess that reverting those cases is still faster than applying suggestions manually. Feel free to remove the corresponding problematic lines and use the replacements for those cases instead.

    The above command does not include the replacement of hasNext and hasNotNext as the replacement is depending on the type of the subject of the assertion. Please use the suggested replacements in the @Deprecated annotation.

    Please have a look at older release-notes in case you don't migrate from 0.12.0 - 0.16.0

    Sponsors

    We would like to thank Tegonal GmbH for sponsoring Support and PR-Review time. And also a big thank you for sponsoring some time to speed up the transition to the new to + infitive naming schema 🙃

    Are you are using Atrium at work?

    Please consider to support the project as well by:

    • sponsoring robstoll (Author and main contributor)
    • share your expectation functions with others
    • report bugs
    • provide feedback in case you miss a feature
    Source code(tar.gz)
    Source code(zip)
  • v0.17.0-RC1(Jul 13, 2021)

    Official release notes will be done with v0.17.0 (see https://github.com/robstoll/atrium/wiki/Release-notes-0.17.0-draft in the meantime)

    Source code(tar.gz)
    Source code(zip)
  • v0.16.0(Mar 30, 2021)

    Table of Content

    New Features

    All APIs

    • #765 Path.isSymbolicLink => thanks to @fimbulwint for the implementation
    • #766 Path.isEmptyDirectory => thanks to @mai2412 for the implementation
    • #837 introduce its as convenience feature extractor at the cost of poorer reporting

    api-fluent-en_GB

    • no fluent only additions this time

    api-infx-en_GB

    • no infix only additions this time

    Logic / Core

    • #586 refactor core, don't use ServiceLoader
      • introduced experimental ComponentFactoryContainer which replaces the current ServiceLoader approach
      • moved most functionality from core-robstoll-lib to atrium-api-core (though in impl packages and not exposed via module-info, create an issue if you have the need to use an implementation directly)

    Fixes

    • #771 contains all no longer working in infix
    • #777 containsNoDuplicate reports has duplicates => reports now has not: duplicate elements

    Improvements

    • #677 add samples for iteratorAssertions of api-infix => thanks to @lilimapradhan9
    • #678 add samples for listAssertions of api-infix => thanks to @lilimapradhan9 and thanks to @szatyinadam for a fix
    • #676 add samples for comparableAssertions of api-infix => thanks to @mai2412
    • #680 add samples for collectionAssertions of api-infix => thanks to @szatyinadam
    • #741 Improve the Documentation for because => thanks to @jGleitz
    • #742 Remove ‘@throws AssertionError’ KDoc from Assertion Functions => thanks to @jGleitz
    • #756 create PR with generated README examples on master if necessary
    • #746 Change Wording from ‘Assertion’ to ‘Expectation’ => thanks to @jGleitz for the idea and part of the work
    • #806 filter out io.kotest runner from RemoveRunnerAtriumError => thanks to @mai2412 for the implementation
    • #803 don't depend on jcenter

    Deprecation

    • VarArgHelper, Group, and helper functions in package ch.tutteli.atrium.domain.builders.utils; they will all be removed with 0.17.0 => use the equivalent from atrium-logic (search & replace import)
    • moved PleaseUseReplacementException from domain-builders to core
    • evalOnce; will be removed with 0.17.0 without replacement.
    • SubjectProvider; will be removed with 0.17.0 without replacement. => use Expect on the API level and AssertionContainer on the logic level
    • AssertionBuilder.createDescriptive => use _logic.createDescriptiveAssertion instead
    • AssertionContainer.collectForDifferentSubject => use collectBasedOnDifferentSubject
    • FeatureExpectOptionsChooser from atrium-core => use the one from atrium-logic instead
    • Reporter.atriumErrorAdjuster; will be removed with 0.17.0 => use ComponentFactoryContainer instead
    • MethodCallFormatter.format; will be removed with 0.17.0 => use formatCall
    • Feature.description and FeatureWithCreator.description in api-infix; will be removed with 0.17.0 => use descriptionProvider instead
    • ReporterFactory and reporter => use ComponentFactoryContainer instead
    • ReporterBuilder including all steps => use ComponentFactoryContainer instead
    • ExpectBuilder including all steps => use RootExpectBuilder from atrium-logic
    • MetaFeature; will be removed with 0.17.0 => use MetaFeature from atrium-logic
    • MetaFeatureBuilder; will be removed with 0.17.0 without replacement
    • MetaFeatureOption; will be removed with 0.17.0 => use MetaFeatureOption from the API

    Deprecations with 0.17.0

    Basically all assertion functions in api-fluent (and maybe also in api-infix) as we move to a consistent to + infinitive naming schema in api-fluent. We might deprecate all translation enum entries which do not follow the to+infinitive schema but since we plan to rename those in 0.18.0 anyway, we might also wait until then.

    Breaking Changes

    Planned (previously deprecated or announced)

    see #755

    • removed deprecated APIS and bundles
    • removed deprecated functions in domain-api, domain-robstoll, domain-robstoll-lib and domain-builders
    • removed deprecated atrium-spec
    • removed other deprecated modules

    moreover:

    • #773 we do no longer build against jdk 9, jdk10, jdk12, jdk13 or in other words we drop support for those versions => update to jdk 11 or stick with jdk8
    • removed AssertionHolder -> changed DelegatingExpect accordingly, expects now an AssertionContainer
    • removed DescriptionMapAssertion
    • parameter objects extending Group from domain-builders now extend Group from builders => binary backward compatibility break - most likely you will not notice anything. If you should have used own types extending Group, then switch to atrium-logic as well.
    • we removed deprecated ...Assertions as well as other deprecating stuff on the logic level.

    Unplanned

    • removed stuff in core as well, which was already deprecated and only used by the above deprecated functionality. This was not announced prior but we doubt that someone will be affected.
    • made BasicAssertionGroup, ExplanatoryAssertionGroup, InvisibleAssertionGroup, BasicExplanatoryAssertion, BasicDescriptiveAssertion and EmptyNameAndRepresentationAssertionGroup internal
    • removed deprecated translations which were scheduled to be removed with 1.0.0, we kept them long enough.
    • made constructor of ArgumentMapperBuilder internal => use mapArguments instead
    • moved MetaFeature to atrium-logic and MetaFeatureOption to the APIs, this is a binary backward compatible break only => please re-compile.

    Breaking Changes with 0.17.0

    First of all, see deprecation above which will be removed with 0.17.0, some of them are breaking as well

    • we will remove RawString => switch to Text
    • we will remove StringBasedRawString => switch to Text
    • we will remove Untranslatable.constructor which expect CharSequence and a lambda => use the primary constructor which expects a string
    • we will remove Feature.description and FeatureWithCreator.description in api-infix => use descriptionProvider instead
    • we will remove domain-builders => switch to atrium-logic
    • we will remove domain-robstoll and domain-robstoll-lib => switch to atrium-logic
    • we will remove core-robstoll => create components via ComponentFactoryContainer (still experimental currently thought)
    • we will remove core-robstoll-lib => implementation is now part of core
    • not really a breaking change but just as info, we will most likely rename core-api to core (as we no longer split core into api and implementation)
    • might be that we need to change package names which is a binary and source backward compatibility break in case we switch to the new Kotlin MPP plugin with 0.17.0

    Breaking Changes with 1.0.0

    See atrium-roadmap -> Milestone 1.0.0

    Migrating deprecated functionality

    The following command is carrying out some search & replace so that you don't need to rely on the (unfortunately sometimes buggy) behaviour of ReplaceWith.

    find ./ -path "*/test/*" -name "*.kt" | xargs perl -0777 -i \
    -pe 's/import ch.tutteli.atrium.domain.builders.utils/import ch.tutteli.atrium.logic.utils/g;' 
    

    Please have a look at older release-notes in case you don't migrate from 0.12.0 - 0.15.0

    Sponsors

    We would like to thank Tegonal GmbH for sponsoring Support and PR-Review time.

    Are you are using Atrium at work?

    Please consider to support the project as well by:

    • sponsoring robstoll (Author and main contributor)
    • share your expectation functions with others
    • report bugs
    • provide feedback in case you miss a feature
    Source code(tar.gz)
    Source code(zip)
  • v0.15.0(Dec 23, 2020)

    Table of Content

    New Features

    All APIs

    • #173 MapLike.contains including sophisticated builder (also for inOrder.only)
    • #68 MapLike.containsOnly
    • #733 MapLike.size
    • #660 add Expect.because to document the reason for an assertion => thanks to @Valefant for most of the implementation and @jGleitz for the proposition and discussion.

    api-fluent-en_GB

    • no fluent only additions this time

    api-infx-en_GB

    • no infix only additions this time

    Logic / Core

    • #711 turn MapAssertions into MapLikeAssertions

    Fixes

    • #719 fix link to examples in README

    Improvements

    • #717 reword entry in reporting of Iterable.contains to element
    • #720 don't show the feature value in an explanatory assertion group
    • #736 don't show the down-cast in toBeNullIfNullGivenElse
    • #738 use the info bullet point for unexpected Exceptions
    • #739 use info bullet point for notice in BigDecimal.isEqualIncludingScale
    • add link to output of examples in README
    • list third-party extensions in README => create a pull request if you are a maintainer of an extension, we happily add yours as well.

    Breaking Changes

    Planned (previously deprecated or announced)

    • none this time

    Unplanned

    • none this time

    Deprecation

    • AnyAssertions.toBeNull on the logic level; will be removed with 0.16.0 => use toBe(null)
    • DescriptionMapAssertion; will be removed with 0.16.0 => use DescriptionMapLikeAssertion

    Deprecations with 0.16.0

    • we will transform the api-fluent to a consistent to + infinitive syntax and will therefore deprecate most of the assertion functions, see https://github.com/robstoll/atrium-roadmap/issues/93 for more information.

    Breaking Changes with 0.16.0

    • we will remove all deprecated APIs (you need to migrate to api-fluent or api-infix if you want to use this version or onwards, see older release notes for migration hints and scripts).
    • we will also remove deprecated stuff on the domain/core level where they only remained due to the deprecated APIs.
    • remove AnyAssertions.toBeNullIfNullGivenElse which expects a kClass => use the one which does not
    • might be we have to break compatibility when we deprecate core-robstoll and core-robstoll-lib
    • might also be, that we have to break compatibility when we move ExpectBuilder and ExpectOptions from domain-builders to atrium-logic
    • we will break binary compatibility when we move Group from atrium-domain-builders to atrium-logic (or to the individual APIs)
    • we will break binary compatibility when we move VarArgHelper from atrium-domain-builders to atrium-logic
    • we might skip the process of deprecating stuff on the logic level. We will rename ...Assertions to ...Expectations
    • we will rename files on the API level which is a binary break
    • might be that we need to change package names which is a binary and source backward compatibility break in case we switch to the new Kotlin MPP plugin with 0.16.0
    • we will drop support for Java 9 and 10 (we will build 0.16.0 with jdk11) => update to jdk 11 or stick with jdk8
    • we will no longer check if compatible with jdk 12 or 13 => use jdk11 (or jdk14 but we will move to jdk15 as soon as jacoco supports it)

    Breaking Changes with 1.0.0

    See atrium-roadmap -> Milestone 1.0.0

    Migrating deprecated functionality

    We provide @Deprecated annotations with ReplaceWith -> use the suggestions as advertised.

    Please have a look at older release-notes in case you don't migrate from 0.12.0, 0.13.0 or 0.14.0

    Sponsors

    We would like to thank Tegonal GmbH for sponsoring Support and PR-Review time.

    Are you are using Atrium at work?

    Please consider to support the project as well by:

    • sponsoring robstoll (Author and main contributor)
    • share your assertion functions with others
    • report bugs
    • provide feedback in case you miss a feature
    Source code(tar.gz)
    Source code(zip)
  • v0.14.0(Nov 14, 2020)

    Table of Content

    New Features

    All APIs

    • #461 Sequence.asList => thanks to @Valefant for the implementation
    • #462 Iterable.asList => thanks to @Valefant for the implementation
    • #172 Iterable.containsNoDuplicates => thanks to @frikit for the implementation
    • #480 accept date as string in ISO 8601 format for ChronoLocalDate => thanks to @Valefant for implementation
    • #482 accept date and time as string in ISO 8601 for ChronoZonedDateTime => thanks to @Valefant for implementation
    • #591 Path.isExecutable => thanks to @jGleitz for the implementation
    • #629 Path.isAbsolute => thanks to @jakubriegel for the implementation
    • #630 Path.isRelative => thanks to @jakubriegel for the implementation
    • #590 Path.hasDirectoryEntry => thanks to @jgrgt for the first implementation (api-fluent) and @jGleitz for the completion (generalise to logic and add to infix)

    api-fluent-en_GB

    • no fluent only additions this time

    api-infx-en_GB

    • no infix only additions this time

    Domain / Core

    • we transition away from atrium-domain-... to atrium-logic, getting rid of ServiceLoader and other changes. Some parts of domain were already deprecated (see below)

    Fixes

    • #631 Atrium stops working for JS with Kotlin 1.4 => thanks to @chadmarchand for the fix

    Improvements

    • #628 Report Evaluation Result After notToThrow(), catch exceptions in feature extraction
    • #329 fuse FeatureAssertionSpec with AssertionSpec => thanks to @tarczynskitomek for the first few adjustments
    • #349 configure dependabot to update samples => thanks to @uzilan for the implementation
    • #400 check if Js isIntance bug is really fixed in Kotlin => thanks to @Valefant for the time and simplification
    • #409 limit representation to 10'000 chars => thanks to @Valefant for the implementation
    • #600 make generateLogic more readable with named matches => thanks to @jgrgt for the suggestion and implementation
    • #637 update gradle wrapper => thanks to @fejd
    • #473 update js samples to use the new org.jetbrains.kotlin.js gradle plugin => thanks to @chadmarchand for most of the work
    • #643 add samples for anyAssertions => thanks to @jdornieden
    • #644 add samples for arrayAssertions => thanks to @jdornieden
    • #646 add samples for collectionAssertions => thanks to @Gosunet
    • #647 add samples for comparableAssertions => thanks to @Gosunet
    • #649 add samples for floatingPointAssertions => thanks to @BimuratMukhtar
    • #652 add samples for iteratorAssertions => thanks to @Gosunet
    • #653 add samples for listAssertions => thanks to @BimuratMukhtar
    • #656 add samples for pairAssertions => thanks to @BimuratMukhtar

    Breaking Changes

    Planned (previously deprecated or announced)

    • we are no longer using the builders defined in the API for CharSequence/Iterable.contains but the new ones from atrium-logic. This constitutes a binary backward compatibility break. Please re-compile, it is still source compatible.

    Unplanned

    • we are no longer using the builders defined in atrium-domain-builders for a subject change and feature extraction but the once from atrium-logic. This constitutes a binary backward compatibility break. Please re-compile, it is still source compatible.

    Deprecation

    • the helper function asExpect to turn an Assert<T> into an Expect<T> => time to move on :wink:
    • most of the module atrium-domain-builders -> use atrium-logic instead, see migration hints further down below. Inter alia:
      • SubjectChanger and SubjectChangerBuilder
      • FeatureExtractor and FeatureExtractorBuilder
      • toVarArg, iterableLikeToIterable
      • nullable, nullableContainer, nullableKeyMap etc.
      • mapArguments

    Breaking Changes with 0.15.0

    • might be we have to break compatibility when we deprecate core-robstoll and core-robstoll-lib
    • might also be, that we have to break compatibility when we move ExpectBuilder and ExpectOptions from domain-builders to atrium-logic
    • we will break binary compatibility when we move Group from atrium-domain-builders to atrium-logic (or to the individual APIs)
    • we will break binary compatibility when we move VarArgHelper from atrium-domain-builders to atrium-logic

    Breaking Changes with 1.0.0

    See atrium-roadmap -> Milestone 1.0.0

    Migrating deprecated functionality

    We don't provide ReplaceWith to transition from atrium-domain-robstoll/-robstoll-lib to atrium-logic in all cases. We don't expect that there are enough users using types of those modules directly. Let us know if you do and have troubles migrating.

    In case you should use:

    • mapArguments, then search for import ch.tutteli.atrium.domain.builders.utils.mapArguments and replace it with import ch.tutteli.atrium.logic.utils.mapArguments
    • nullable, then search for import ch.tutteli.atrium.domain.builders.utils.nullable and replace with import ch.tutteli.atrium.logic.utils.nullable
    • subExpect, then search for subExpect and replace with expectLambda and search for import ch.tutteli.atrium.domain.builders.utils.subExpect and replace with import ch.tutteli.atrium.logic.utils.expectLambda

    Some further search&replace patterns for assertion writers, in case you use:

    • CharSequenceOrNumberOrChar or CharSequenceOrNumberOrChar, search for import ch.tutteli.atrium.domain.creating.typeutils and replace with import ch.tutteli.atrium.logic.creating.typeutils

    In case you already started using the experimental atrium-logic module, then you might run into the problem that:

    • getExpectOfFeature() does not exist => replace with transform()
    • addToInitial does not exist => replace with collectAndAppend
    • we removed genericSubjectBasedFeature and genericFeature from FeatureAssertions -> use either manualFeature or FeatureExtractor/FeatureExtractorBuilder instead

    Please have a look at the older release-notes in case you don't migrate from 0.12.0 or 0.13.0

    Sponsors

    We would like to thank Tegonal GmbH for sponsoring Support and PR-Review time.

    Are you are using Atrium at work?

    Please consider to support the project as well by:

    • sponsoring robstoll (Author and main contributor)
    • share your assertion functions with others
    • report bugs
    • provide feedback in case you miss a feature
    Source code(tar.gz)
    Source code(zip)
  • v0.13.0(Aug 27, 2020)

    Table of Content

    New Features

    All APIs

    • #460 add Iterator.hasNext/hasNotNext => thanks to @mspear for the implementation
    • #484 introduce Comparable.isEqualComparingTo => thanks to @jaibhavaya for the implementation
    • #180 Any.isNoneOf/isNotIn=> thanks to @anesabml for the implementation
    • #479 Allow that Sequence and Array can be passed to elementsOf => thanks to @anesabml for the implementation
    • #481 accept date and time as string in ISO 8601 format for ChronoLocalDateTime => thanks to @rkiselev for the implementation of the fluent API

    api-fluent-en_GB

    • no fluent only additions this time

    api-infx-en_GB

    • Path.hasSameTextualContentAs added to infix API as well => thanks to @z13z for most of the work

    Domain / Core

    • we transition away from atrium-domain-... to atrium-logic, getting rid of ServiceLoader and other changes. Some parts of domain where already deprecated. Stay tuned, documentation will be updated with 0.14.0

    Fixes

    • #515 Path assertions use wrong description

    Improvements

    • #234 remove limit of stacktrace
    • #483 adjust reporting for isLess/GreaterThanOrEqual => thanks to @z13z for the implementation
    • #477 deprecate defaultTranslationOf(IgnoringCase) => thanks to @z13z for the implementation
    • #337 filter jasmine out of error-stacks => thanks to @z13z for part of the work

    Breaking Changes

    Planned (previously deprecated or announced)

    • none this time

    Unplanned

    • we are treating Translatable now special in the ObjectFormatter which does not make it necessary to wrap it into a RawString. This is a breaking change depending on how you have defined your Translatable. In case it was an Enum and you formatted it without wrapping it into a RawString, then it is now translated instead. I assume this should never be the case, so this breaking change is only hypothetical.
    • removed ch.tutteli.atrium.assertions.builders.Explanatory.ExplanationOption#withExplanation(ch.tutteli.atrium.reporting.translating.Translatable) because Translatables are now treated as Text in any case -- this is a binary backward compatibility break, it should suffice to re-compile.
    • removed ch.tutteli.atrium.domain.builders.creating.changers.FeatureExtractorBuilder.RepresentationInCaseOfFailureStep#withRepresentationForFailure(ch.tutteli.atrium.reporting.translating.Translatable) for the same reason
    • removed previousExpect and getAssertions() from FeatureExpect => let me know in case you used it
    • removed experimental config from RootExpect and FeatureExpect

    Deprecation

    • Feature/RootExpectConfig; ; will be removed with 1.0.0
    • CoreFactory... (will all be removed with 1.0.0)
      • newDelegatingReportingAssertionContainer
      • newFeatureExpect
      • newReportingAssertionContainer
      • newCollectingAssertionContainer
    • RawString; will be removed with 1.0.0 -> use Text
    • StringBasedRawString; will be removed with 1.0.0 -> use Text
    • TranslatableBasedRawString; will be removed with 1.0.0 -> no longer required, use Translatable directly
    • ReportingAssertionContainer including factory methods; will be removed with 1.0.0 -> use RootExpect, FeatureExpect or DelegatingExpect instead
    • AssertionChecker and all its variants including factory methods; will be removed with 1.0.0 -> use RootExpect, FeatureExpect or DelegatingExpect instead
    • ch.tutteli.atrium.translations.ErrorMessages -> use ch.tutteli.atrium.creating.ErrorMessages instead
    • ch.tutteli.atrium.domain.robstoll.lib.creating.throwable.thrown.creators.ThrowableThrownFailureHandler -> use ch.tutteli.atrium.logic.impl.creating.changers.ThrowableThrownFailureHandler instead
    • the modules atrium-domain-robstoll and atrium-domain-robstoll-lib -> use atrium-logic instead
    • all assertion interfaces in domain-api except for CharSequence/IterableAssertions, they will be deprecated in 0.14.0
      • correspondingly all builders in domain-builders, impl classes in domain-robstoll and impl functions in domain-robstoll-lib (basically almost everything in domain-robstoll and domain-robstoll-lib is deprecated -> use atrium-logic instead)

    Breaking Changes with 0.14.0

    • we are going to replace IterableContains with IterableLikeContains, this is a binary backward compatibility break. Source compatibility should still hold. IterableLikeContains will path the way to use it with Array types.
    • we are going to replace CharSequenceContains from domain with CharSequence from logic, this is a binary backward compatibility break. Source compatibility should still hold.
    • the new module atrium-logic introduced with this version is not stable yet, we most likely break things with 0.14.0. Planned is to replace CollectionAssertions with CollectionLikeAssertions.

    Breaking Changes with 1.0.0

    See atrium-roadmap -> Milestone 1.0.0

    Migrating deprecated functionality

    There a few deprecations in this version (see above) -- use the ReplaceWith provided in the @Depreacted annotations.

    Please have a look at the older release-notes in case you don't migrate from 0.12.0

    Sponsors

    We would like to thank Tegonal GmbH for sponsoring Support and PR-Review time as well as the extra hours to finish the open PRs which should made it into v0.13.0.

    Are you are using Atrium at work?

    Please consider to support the project as well by:

    • sponsoring robstoll (Author and main contributor)
    • report bugs
    • share your assertion functions with others
    Source code(tar.gz)
    Source code(zip)
  • v0.12.0(May 26, 2020)

    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-fluent-en_GB

    • #375 Path.hasSameTextualContentAs => @thanks to tkech17

    new api-infix-en_GB

    Thanks to the following contributors to support the migration from cc-infix-en_GB to infix-en_GB:

    • #227 collectionAssertions => thanks to @ratkayandras
    • #231 pairAssertions => thanks to @dexpota
    • #228 listAssertions => thanks to @sanatik
    • #232 sequenceAssertions => thanks to @ivanmiklec
    • #229 mapAssertions => thanks to @jlundhol
    • #230 mapEntryAssertions => thanks to @johnGachihi
    • #226 arrayAssertions => thanks to @isfedorov
    • #233 bigDecimalAssertions => thanks to @isfedorov
    • #236 create bundles for the new infix API => thanks to @isfedorov
    • #316 fileAssertions => thanks to @jGleitz
    • #317 localDateAssertions => thanks to @jGleitz
    • #318 localDateTimeAssertions => thanks to @jGleitz
    • #319 zonedDateTimeAssertions=> thanks to @tfesenko
    • #313 chronoLocalDateAssertions => thanks to @ivanmiklec
    • #314 chronoLocalDateTimeAssertions => thanks to @isfedorov
    • #315 chronoZonedDateTimeAssertions => thanks to @tfesenko
    • #388 optionalAssertions => thanks to @isfedorov

    kotlin 1.3 extensions

    • #389 resultAssertions => thanks to @ivanmiklec

    Domain / Core

    • none this time

    Fixes

    • none this time

    Improvements

    • #235 setup code coverage for windows=> thanks to @ivanmiklec
    • #245 Build samples as composite build => thanks to @assaflei #/167 compile android class files into dex bytecode => thanks to @assaflei
    • #493 merge jdk8 extension into jvm module => thanks to @assaflei
    • #470 create maven sample project => thanks to @binkley
    • #466 fail if a TranslatableWithArgs has not exact arguments => thanks to @tkech17
    • #495 cache gradle and dependencies in github actions => thanks to @assaflei
    • #506 remove android jars -> they are no longer required => thanks to @ultraon for checking if the module-info.class bug was really resolved in d8

    Breaking Changes

    Planned (previously deprecated or announced)

    • none this time (yet we no longer publish a dedicated -android jar you can use the regular jvm artifact instead)

    Unplanned

    • none this time

    Deprecation

    The following was deprecated and will be removed with 1.0.0:

    • API cc-infix-en_GB => use infix-en_GB, fluent-en_GB respectively => a big thank you to @Miftahunajat for the many ReplaceWith he placed all over api-cc-infix to ease the migration :+1:
    • the jdk8 extension was merged into the normal module => search import ch.tutteli.atrium.api.fluent.en_GB.jdk8 replace with import ch.tutteli.atrium.api.fluent.en_GB

    Breaking Changes with 1.0.0

    See atrium-roadmap -> Milestone 1.0.0

    Migrating deprecated functionality

    API fluent

    There aren't any deprecations in api-fluent-en_GB in this version and thus also no migration required if you update from 0.9.x, 0.10.0 or 0.11.0

    In case you used the jdk8 extension: it was merged into the main artifact:

    • perform a search for import ch.tutteli.atrium.api.fluent.en_GB.jdk8 and replace with import ch.tutteli.atrium.api.fluent.en_GB
    • remove the corresponding dependency

    API infix

    You should migrate from api-cc-infix-en_GB to the new api-infix-en_GB (api-cc-infix-en_GB will be removed with 1.0.0) In case you migrate from a version < 0.7.0 to this version, then please have a look at the migration guide given in the Release notes of v0.7.0 and v0.8.0 first.

    Otherwise you can use the suggested replacements (ALT + Enter -> Replace with ...) or the search/replace patterns shown below (recommended, since faster).

    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 14 as described below. The script for 15a or 15b follows afterwards (don't forget to do the manual points 16, 17, ...), 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/(\.| )((?:toThrow|isA)<.*>)\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\.infix\.en_GB/import ch.tutteli.atrium.api.infix.en_GB/g;' \
    -pe 's/is(Less|Greater)OrEquals/is$1ThanOrEqual/g;' \
    -pe 's/\.asIterable\(\)/ asList o/g;' \
    -pe 's/asIterable(\s*\{)/asList$1/g;' \
    -pe 's/\.asEntries\(\)/ asEntries o/g;' \
    -pe 's/ o / it /g;' \
    -pe 's/get\s*Index\(([^\)]+)\)\s*assertIt\s*\{([^\}]+)\}/get index($1) {$2}/g;' \
    -pe 's/import ch.tutteli.atrium.api.infix.en_GB.Index/import ch.tutteli.atrium.api.infix.en_GB.index/g;' \
    -pe 's/getExisting\s*Key\(([^\)]+)\)\s*assertIt\s*\{([^\}]+)\}/getExisting key($1) {$2}/g;' \
    -pe 's/import ch.tutteli.atrium.api.infix.en_GB.Key/import ch.tutteli.atrium.api.infix.en_GB.key/g;' 
    

    In case you use Kotlin 1.4 or have enabled the new type inference, then you can use the following script to carry out point 13a:

    find ./ -path "*/test/*" -name "*.kt" | xargs perl -0777 -i \
    -pe 's/((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\((?:[\n\r\s]*)subject::([^\)]+)\)(\s*\{)/$1its feature of(\{ f(it::$2) \})$3/g;' \
    -pe 's/(?:property|returnValueOf|rueckgabewertVon)\((?:[\n\r\s]*)subject::([^\)]+)\)(\s*\{)/feature of(\{ f(it::$1) \})$2/g;' \
    -pe 's/((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\((?:[\n\r\s]*)subject::([^\)]+)\)/$1its feature \{ f(it::$2) \}/g;' \
    -pe 's/(?:property|returnValueOf|rueckgabewertVon)\((?:[\n\r\s]*)subject::([^\)]+)\)/feature \{ f(it::$1) \}/g;' \
    -pe 's/((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\(([A-Z][^:]+)::([^\)]+)\)(\s*\{)/$1its feature of($2::$3)$4/g;' \
    -pe 's/(?:property|returnValueOf|rueckgabewertVon)\(([A-Z][^:]+)::([^\)]+)\)(\s*\{)/feature of($1::$2)$3/g;' \
    -pe 's/((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\(([A-Z][^:]+)::([^\),]+)\)/$1 its feature($2::$3)/g;' \
    -pe 's/(?:property|returnValueOf|rueckgabewertVon)\(([A-Z][^:]+)::([^\),]+)\)/feature($1::$2)/g;' \
    -pe 's/((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\(([A-Z][^:]+)::([^\)]+)\)/$1its feature of($2::$3)/g;' \
    -pe 's/(?:property|returnValueOf|rueckgabewertVon)\(([A-Z][^:]+)::([^\)]+)\)/feature of($1::$2)/g;' \
    -pe 's/((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\(([^:]+)::([^\)]+)\)/$1its feature of("$2.$3") { $2.$3 }/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;' \
    

    Otherwise you need to carry out the point 13b which can be done with the following script`

    find ./ -path "*/test/*" -name "*.kt" | xargs perl -0777 -i \
    -pe 's/((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\((?:[\n\r\s]*)subject::([^\)]+)\)(\s*\{)/$1its feature \{ f(it::$2) \} it$3/g;' \
    -pe 's/(?:property|returnValueOf|rueckgabewertVon)\((?:[\n\r\s]*)subject::([^\)]+)\)(\s*\{)/feature \{ f(it::$1) \} it$2/g;' \
    -pe 's/((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\((?:[\n\r\s]*)subject::([^\)]+)\)/$1its feature \{ f(it::$2) \}/g;' \
    -pe 's/(?:property|returnValueOf|rueckgabewertVon)\((?:[\n\r\s]*)subject::([^\)]+)\)/feature \{ f(it::$1) \}/g;' \
    -pe 's/((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\(([A-Z][^:]+)::([^\)]+)\)(\s*\{)/$1its feature of($2::$3)$4/g;' \
    -pe 's/(?:property|returnValueOf|rueckgabewertVon)\(([A-Z][^:]+)::([^\)]+)\)(\s*\{)/feature of($1::$2)$3/g;' \
    -pe 's/((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\(([A-Z][^:]+)::([^\),]+)\)/$1 its feature($2::$3)/g;' \
    -pe 's/(?:property|returnValueOf|rueckgabewertVon)\(([A-Z][^:]+)::([^\),]+)\)/feature($1::$2)/g;' \
    -pe 's/((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\(([A-Z][^:]+)::([^\)]+)\)/$1its feature of($2::$3)/g;' \
    -pe 's/(?:property|returnValueOf|rueckgabewertVon)\(([A-Z][^:]+)::([^\)]+)\)/feature of($1::$2)/g;' \
    -pe 's/((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\(([^:]+)::([^\)]+)\)/$1its feature of("$2.$3") { $2.$3 }/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;'
    

    But you still need to add manually import ch.tutteli.atrium.api.infix.en_GB.workaround.it where it is not known


    In case you cannot carry out the commands above, then read on to perform the manual steps:

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

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

    5. 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
    6. 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

    7. Switch from AssertImpl to ExpectImpl

      Search: AssertImpl Replace: ExpectImpl

    8. 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<

    9. Switch the API

      Search: import ch.tutteli\.atrium\.api\.cc\.infix\.en_GB Replace:import ch.tutteli.atrium.api.infix`

    10. isLessOr/isGreaterOrEquals

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

    11 use asList instead of asIterable and replace asEntries()

    Search: \.asIterable\(\) Replace: asList o

    Search: asIterable(\s*\{) Replace: asList$1

    Search: asEntries\(\) Replace: asEntries o

    12 List get Index assertIt This one has to be done with care as there could be nested assertion group blocks

    Search: get\s*Index\(([^\)]+)\)\s*assertIt\s*\{([^\}]+)\} Replace: get index($1) {$2}

    Search: import ch.tutteli.atrium.api.infix.en_GB.Index Replace: import ch.tutteli.atrium.api.infix.en_GB.index

    13 Map getExisting Key assertIt This one has to be done with care as there could be nested assertion group blocks

    Search: getExisting\s*Key\(([^\)]+)\)\s*assertIt\s*\{([^\}]+)\} Replace: getExisting key($1) {$2}

    Search: import ch.tutteli.atrium.api.infix.en_GB.Key Replace: import ch.tutteli.atrium.api.infix.en_GB.key

    14 use it instead of o inside assertion groups: Search: o Replace: it (you can also replace by its)

    15a. use new feature mechanism - replacements for Kotlin 1.4 or the new inference (skip to 13b in case you use Kotlin < 1.4)

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

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

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

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

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

    Search: ((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\(([A-Z][^:]+)::([^\)]+)\)(\s*\{) Replace: $1its feature of($2::$3)$4

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

    Search: ((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\(([A-Z][^:]+)::([^\),]+)\) Replace: $1 its feature($2::$3)

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

    Search: ((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\(([A-Z][^:]+)::([^\)]+)\) Replace: $1its feature of($2::$3)

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

    Search: ((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\(([^:]+)::([^\)]+)\) Replace: $1its feature of("$2.$3") { $2.$3 }

    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

    15b use new feature mechanism - replacements for Kotlin < 1.4 (skip if you already applied 11a)

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

    Search: ((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\((?:[\n\r\s]*)subject::([^\)]+)\)(\s*\{) add import ch.tutteli.atrium.api.infix.en_GB.workaround.it to those files

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

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

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

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

    Search: ((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\(([A-Z][^:]+)::([^\)]+)\)(\s*\{) Replace: $1its feature of($2::$3)$4

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

    Search: ((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\(([A-Z][^:]+)::([^\),]+)\) Replace: $1 its feature($2::$3)

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

    Search: ((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\(([A-Z][^:]+)::([^\)]+)\) Replace: $1its feature of($2::$3)

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

    Search: ((?:[\n\r]+|\{)\s*)(?:property|returnValueOf|rueckgabewertVon)\(([^:]+)::([^\)]+)\) Replace: $1its feature of("$2.$3") { $2.$3 }

    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

    But you still need to add manually import ch.tutteli.atrium.api.infix.en_GB.workaround.it where it is not known

    16 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 any more 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)
    Source code(tar.gz)
    Source code(zip)
  • v0.11.1(Apr 30, 2020)

  • v0.11.0(Apr 19, 2020)

    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 and with 0.9.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 fluent-en_GB

    • #131 shortcut Iterable.containsExactlyElementsOf => thanks to @tkech17
    • #425 shortcut Iterable.containsElementsOf => thanks to @tkech17
    • #422 add elementsOf to contains.ignoreCase => thanks to @Miftahunajat

    Domain / Core

    • none this time

    Fixes

    • none this time

    Improvements

    • #339 Avoid Duplicated Messages
    • #116 Migrate Spek1 specs to Spek2 => thanks to @assaflei
    • #416 move non-jvm specific specs to common => thanks to @assaflei

    Breaking Changes

    Planned (previously deprecated or announced)

    • none this time

    Unplanned

    • none this time

    Deprecation

    • none this time

    Breaking Changes with 1.0.0

    See atrium-roadmap -> Milestone 1.0.0

    Migrating deprecated functionality

    There aren't any deprecations in this version and thus also no migration required if you update from 0.9.x or 0.10.0.

    In case you migrate from 0.8.0 to this version, have a look at the migration guide given in the release notes of v0.9.0

    Source code(tar.gz)
    Source code(zip)
  • v0.10.0(Mar 12, 2020)

    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 fluent-en_GB

    • #31 shortcut for Throwable.cause => thanks to @tfesenko for the implementation

    jdk8 extensions

    • #216 shortcut for Path.resolve => thanks to @slalu for the implementation

    Domain / Core

    • none this time

    Fixes

    • none this time

    Improvements

    Breaking Changes

    Planned (previously deprecated or announced)

    • none this time

    Unplanned

    • none this time

    Deprecation

    • none this time

    Breaking Changes with 1.0.0

    See atrium-roadmap -> Milestone 1.0.0

    Migrating deprecated functionality

    There aren't any deprecations in this version and thus also no migration required if you update from 0.9.0.

    In case you migrate from 0.8.0 to this version, have a look at the migration guide given in the release notes of v0.9.0

    Source code(tar.gz)
    Source code(zip)
  • v0.9.2(Feb 8, 2020)

    Fixes

    it was planned that withOptions ships with 0.9.0 (see #153). However, this was done only for the assertion verb which is used internally in Atrium but was not revealed for the predefined assertion verbs, hence this patch-version (thanks to @matejdro for the report - #362)

    Most users won't use withOptions and thus we are going to present only withRepresentation for now (introduced in 0.9.2 #365). ❗❗Note though that withOptions and withRepresentation are both experimental and might be changed in a future version without previous notice nor migration path.

    withRepresentation can be used as follows :

    expect(listOf(1,2,2,3 /* imagine a lot more numbers */))
      .withRepresentation("xy numbers")
      .all { isLessThan(10) }
    

    The error report looks then as follows in case of a failure:

    expected that subject: xy numbers
    ◆ all entries: 
        » is less than: 10        (kotlin.Int <1234789>)
        ❗❗ following entries were mismatched: 
           ⚬ index 101: 12        (kotlin.Int <8933389>)
           ⚬ index 223: 10        (kotlin.Int <4523459>)
    

    To use it, you have to add the following annotation to your test method/class @UseExperimental(ExperimentalWithOptions::class)

    Further Improvements

    • #81 create sample MPP project => thanks to @bsemexan for most of the work

    all other features as well a migration guide is given in the release notes of v0.9.0

    Source code(tar.gz)
    Source code(zip)
  • v0.9.1(Feb 4, 2020)

  • v0.9.0(Feb 2, 2020)

    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

    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

    • #203 Result.isSuccess => thanks to @shardulsonar
    • #204 Result.isFailure => thanks to @johnGachihi

    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)
    Source code(tar.gz)
    Source code(zip)
  • v0.9.0-alpha2(Jan 11, 2020)

  • v0.9.0-alpha(Aug 29, 2019)

  • v0.8.0(Apr 28, 2019)

    Jar's can be found here: https://bintray.com/robstoll/tutteli-jars/atrium/0.8.0 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. 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

    • #27 containsExactly as replacement for containsStrictly, thanks to @msmoljan for the implementation and thanks to @christophsturm for the idea
    • #33 isNotBlank for CharSequence, thanks to @pt2121 for the implementation
    • #39 make atLeast optional for CharSequence.contains, thanks to @christophsturm for the idea
    • #51 keys and values for Map to postulate assertions about the keys or values of a Map
    • #37 asEntries for Map, thanks to @arjank for the implementation
    • #29/#75 getExisting for Map to postulate assertions about the value of a corresponding key
    • #28 containskey, #59 containsNotKey for Map, thanks to @uaArsen for the implementation
    • #61/#62 contains for Map
    • #30/#76 get for List
    • #53 notToThrow as counterpart of toThrow - thanks to @charleskorn for the idea
    • #65 shortcut property/fun for Pair.first and Pair.second
    • #25 isKeyValue as well as shortcut property/fun key/value for Map.Entry
    • #69 toBe was opened up for nullable subjects (accepts now Any?), thanks to @dave08 for the discussion
    • #70 toBeNullIfNullElse for nullable subjects, thanks to @dave08 for the idea
    • #71 shortcut property/fun Collection.size
    • #78 containsExactly with single assertion creator
    • #48 asIterable with assertionCreator block
    • #46 o as alternative to this in sub-assertions for the infix API

    Domain / Core

    Features for assertion-function-writers:

    • #67 AssertImpl.mapArguments -> to map a variable length argument lists of the form first: T, vararg rest: T (inside a function T, Array<out T>) to R, Array<out R>
    • #72 changeSubject to a nullable type
    • AssertImpl.feature.extractor -> in case you want to make an assertion about a feature which is not always safe to extract (e.g. List.get expects a suitable index)
    • AssertImpl.collector.collectOrExplain => collects assertions for later usage but only if it is safe to collect them, otherwise it wraps them into an explanatory assertion so that it can be used to enhance reporting
    • AssertImpl.collector.collectNullable which allows to collect assertions for a nullable subject (for AssertionPlantNullable instead of AssertionPlant)

    Others

    • turned Atrium into an multi-platform project; all dependencies are also available for:
      • the JS platform => use the -js suffix; you will have to migrate to bundle en_GB if you still use en_UK, see Migrating deprecated functionality below.
      • the Android platform => use the -android suffix => Thansk to @ultraon for reporting #52 regarding issues with module-info.class
    • #41 deprecated notToBeNullBut for BigDecimal
    • #55 infix API - deprecated calls to toBe if a keyword is passed inadvertently
    • stacktraces in error reporting should no longer contain stack frames of Atrium or test runners

    Fixes

    • none this time

    Improvements

    • DetailedObjectFormatter shows now Kotlin types instead of Java types (e.g. kotlin.Int instead of java.lang.Integer).
    • an AtriumError is now thrown instead of an AssertionError (AtriumError is a subtype of AssertionError)

    Breaking Changes

    Planned (previously deprecated or announced)

    • none this time

    Unplaned

    • Made Group, GroupWithoutNullableEntries and GroupWithNullableEntries invariant. I doubt this will be a problem for someone, otherwise let me know
    • Made Value, Values, Entry, Entries invariant; in case you get problems, try to use user-site variance and specify out there The following breaking changes only bother you if you implemented an own core. Most have been necessary to turn Atrium into a multi-platform project:
    • core uses now an own implementation of Locale and no longer java.util.Locale
    • core uses now KClass instead of Class
    • TranslatableWithArgs takes a List instead of an Array as parameter
    • Reporter needs to provide an AtriumErrorAdjuster in addition
    • removed duplicate anyAssertions in package ch.tutteli.atrium.domain.creating.any.typetransformation.creators (use the one from package ch.tutteli.atrium.domain.creating)

    tl;dr the following is only of interest if you rely on binary compatibility I changed the JvmName of contains? in cc-en_UK and cc-infix-en_UK to containsDeprecated and enthaelt? in cc-de_CH to enthaeltDeprecated due to the DEX compiler for android which cannot handle ? in identifiers. This is a binary backward compatibility break for a method which I introduced in 0.7.0 to retain source backward compatibility. In case you use still use cc-en_UK or cc-infix-en_UK and rely on binary compatibility you will have to recompile when updating to 0.8.0.

    Deprecation

    The following was deprecated and will be removed with 1.0.0:

    • Assert<Iterable>.containsStrictly use containsExactly instead.
    • ReporterBuilder::withoutTranslations using java.util.Locale => use Atrium's Locale
    • TranslatorOption::withDefaultTranslator using java.util.Locale => use Atrium's Locale
    • TextAssertionFormatterOption::withDefaultTextCapabilities => use withTextCapabilities which uses KClass instead of Class
    • AtriumErrorAdjusterOption::withOnlyFailureReporter and withCustomReporter => new step in configuration, use either withDefaultAtriumErrorAdjusters or choose one of the other options
    • all functions containing nullable in their name => their counterpart without nullable in their name where opened up to accept also nullable types (see #60 for details)

    Possible Breaking Changes with 0.9.0

    • I will prepare the transition to Assert<T> instead of Assert<out T>. I will turn Assert into an own type (currently only a type alias) - you should not notice something but it means that the binary code will change when you compile against 0.9.0
    • I might reuse opentest4j exceptions to improve error reporting in IDEs. For this to work I might have to make modifications to Assertion/AssertionGroup (would only affect core implementors).
    • An exception will be thrown where one has to define an assertionCreator -- shall prevent kind of dead code/incomplete assertions; for instance assert(mapOf("a" to 1)).keys {}
      • same for addAssertionsCreatedBy/and {}; they will throw an exception if no sub-assertion is defined
    • toBe, contains etc. which expect T where <T: Any> might be restricted to input types, so that comparing apple with oranges is no longer possible without explicitly stating the type. E.g. assert(1).toBe("hello")would be a compile error

    Possible Breaking Changes with 1.0.0

    Please open an issue if you are not happy with one of the changes and state why or contact me via the Atrium slack channel.

    • Assert<Throwable>.message{} will return Assert<Throwable> instead of Unit

    • Assert<T>.isA{} will return Assert<T> instead of Unit

    • All property and returnValueOf taking an assertionCreator will return the same type as the current subject.

    • I will remove out of Assert<out T> in order that things like asssert(1).toBe("hello") is no longer possible, overloads can be simplified etc.

    • returnValueOf functions might be renamed to returnValueOfX where X denotes the number of arguments. Too often it occurs that Kotlin is not able to infer the correct overload, the user does not get the appropriate help in code completion or the error message is too big. This should help.

    • feature assertion functions might require a lambda in the future. This way error reporting does not blow up in the middle of the way because subject is not available. However, there is a bug concerning nullable-features in Kotlin which prevents me from doing it at the moment: https://youtrack.jetbrains.com/issue/KT-23768, please up-vote it.

    • A type parameter might be added to AssertionGroup to restrict the AssertionGroupType.

    • BulletPointIdentifier together with subtypes (AssertionGroupTypes) might be moved to another package: ch.tutteli.atrium.reporting.assertions

    • AssertionPlant/Assert will switch roles => AssertionPlant will be the typealias of Assert, see #26; should only break binary compatibility

    • I will introduce interface groups for RepoterBuilder as I did in other cases (e.g. see Descriptive); should only break binary compatibility

    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. Otherwise you can use the suggested replacements (ALT + Enter -> Replace with ...)

    Ping me in the Atrium slack channel if you need help.

    Source code(tar.gz)
    Source code(zip)
  • v0.8.0-RC1(Apr 13, 2019)

  • v0.8.0-beta(Apr 13, 2019)

  • v0.8.0-alpha(Jan 19, 2019)

  • v0.7.0-android(Nov 29, 2018)

    See https://github.com/robstoll/atrium/releases/tag/v0.7.0 for full information. This is a patch-fix-version for Android because the DEX compiler cannot handle ? in identifiers and treats module-info.class as normal classes instead of ignoring it.

    v0.8.0 of Atrium will support JS as additional platform and will most probably provide a specific artifact for Android as well. Simplified this means, we won't ship an atrium.jar with 0.8.0-android as version but atrium-android.jar with version 0.8.0.

    Source code(tar.gz)
    Source code(zip)
  • v0.7.0(Jul 20, 2018)

    Jar's can be found here: https://bintray.com/robstoll/tutteli-jars/atrium/0.7.0 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. Yet, I deprecated quite a lot of with this release; please replace deprecated functionality until v1.0.0 where I will remove it. However, I do not provide yet a stable API for the core of Atrium -- it is almost stable, but there might be slight breaking changes which I want to introduce before v1.0.0. That is also the reason why I have not yet established backward compatibility tests for the core. This might affect you if you write your own assertion functions. It also affects you if you provide your own implementation for parts of Atrium.

    New Features

    API

    • isEmpty, isNotEmpty and hasSize for Map

    • messageContains as shortcut function for message { contains(...) }. You can still use message { ... } for more sophisticated assertions. For instance, if a certain string should only be contained once in the message then you can use message { contains.exactly(1).values(...) }. Or in case you want to be sure that something is not contained in the message message { contains(..); containsNot(...) }.

    • toBe(null) as replacement for isNull (isNull will be removed with 1.0.0)

    • notToBeNull as replacement for isNotNull (isNotNull will be removed with 1.0.0)

    • notToBeNullBut as shortuct function for notToBeNull { toBe(...) }

    • asIterable for Sequence and Array

    • added contains.inOrder.only.grouped.within.inAnyOrder to the sophisticated contains assertion builder for Iterable

    • added overloads to property and returnValueOf which allows to use a class reference rather than a bounded reference with the advantage that it is more robust when it comes to error reporting (since it does not need to access the reference to retrieve its name => see https://youtrack.jetbrains.com/issue/KT-23777 for more information) => personally I still use subject::... when I write assertions as such (in tests) but use Class::... inside assertion functions

    • added helper functions for people dealing with Java Code. Per default platform types are turned into a non-nullable version if possible. If you want to turn it into a nullable-version nonetheless, then you have to cast and restate the type. Depending on the type this might be cumbersome. Thus I introduced the following functions which give some extra hint to the compiler without actually adding code (they are all inline functions):

      • nullable turns a type into a nullable type or a function reference into a function reference with a nullable return type.
      • nullableContainer turns an Iterable into an iterable with nullable entry type, likewise it does the same for Array.
      • nullableValueMap turns a Map into a map with a nullable value type.

    Domain / Core

    Features for assertion-function-writers:

    Other

    • further stabilised the API of atrium-core

    • introduced APIs cc-en_GB and cc-infix-en_GB in favour of cc-en_UK and cc-infix-en_UK => see below for migration guidelines

    • introduced atrium-domain modules in favour of atrium-assertion to provide a more stable API concerning impl-functions and sophisticated assertion builders

    • turned most modules into jdk9 modules (deprecated code was placed in separate modules ending with -deprecated)

      • new modules are using ServiceLoader now instead of api-late-binding (due to JDK9 split package problematic, ServiceLoader is a bit slower but on the other hand more commonly used)
    • introduced the module atrium-bc-test which ensures that, specs of 0.6.0:

      • are binary compatible with 0.7.0 (without re-compilation) and
      • can still be compiled against 0.7.0 and run successfully => This does not test every bits and pieces of Atrium, that is, it is only backward compatible to the extend of the tests. => 1.0.0 will contain major BC breaks, see below => Please contact me via slack if you would like to have your tests included in the bc-test (the more the merrier I suppose).

    Fixed

    • #21 Charsequence contains empty string ends in endless loop

    Improvements

    • #14 message of a thrown Exception as well as the beginning of the stack trace is now shown in case it is of a different type than the expected one (gives you some extra hint).
    • #15 Assert is now annotated with a DslMarker which assures that you do not call an assertion function of an outer subject inadvertently.
    • #16 PlantHasNoSubjectException popped up in some cases, is fixed.
    • #17 include actual size for hasSize
    • #20 Iterable<T: Any> contain assertion functions do no longer allow to pass null. Moreover, we added overloads for nullable where missing and renamed them to ...Nullable... (e.g. nullableValue or containsStrictlyNullableValues)
    • #22 Iterable contains in any order values create multiple assertions instead of sub-assertions
    • #23 CharSequence contains.regex should mention regex in reporting
    • predefined assertion verbs are now part of the bundle, newcomers will start off easier using Atrium (Readme was revised as well).
    • documented type assertions (isA) in Readme
    • documented handling of assertions for Sequence and Array in FAQ of Readme
    • the groupId of the artifacts was changed to ch.tutteli.atrium => adjust it next to the version

    Breaking Changes

    Planned (previously deprecated or announced)

    • Was planned but I did not do it: ReporterBuilder::withSameLineTextAssertionFormatter was announced to be removed with 0.7.0 but will remain in the deprecated ReporterBuilder in the deprecated module atrium-assertions and will remain there until 1.0.0. The new ReporterBuilder in module atrium-domain-builders does not include this method.

    Unplaned

    • containsNot checks in addition that the Iterable returns at least one element (or in other words, it fails if Iterable returns nothing).
    • DefaultInvisibleAssertionGroupType was removed, use InvisibleAssertionGroupType instead, use AssertImpl.builder.invisibleGroup respectively.
    • the default implementation for subject was removed from interface BaseReportingAssertionPlant and ReportingAssertionPlantNullable
    • I renamed a few parameter names so that they better reflect what an assertion function expects -> only concerns you if you used named parameters. I assume that no one uses named parameters in this context but let me know if you did because I might introduce more such breaking changes in the future.

    The following changes only bother you if you do not use a bundle module but specify the dependencies manually:

    • removed impl from the module names atrium-core-impl-robstoll and atrium-core-impl-robstoll-lib; => Adjust the names in such a case. E.g. depend on atrium-core-robstoll instead of atrium-core-impl-robstoll (or use one of the bundle modules).
    • some base classes for sophisticated builders where moved from atrium-assertions to atrium-domain-builders; => add a dependency to the domain modules in addition (or use one of the bundle modules).
    • moved deprecated code to seperate modules => add the corresponding dependencies in addition (atrium-translations-en_UK-deprecated, core-api-deprecated, core-robstoll-deprecated)

    Deprecation

    The following was deprecated and will be removed with 1.0.0:

    • the boolean assertion functions isTrue() and isFalse() => use toBe(true), toBe(false) respectively (or re-add as your own function if you want to keep it)
    • the nullable assertion function isNull in API cc-en_UK => use toBe(null) (or re-add as your own function if you want to keep it)
    • the nullable assertion function isNotNull in API cc-en_UK => use noToBeNull (or re-add as your own function if you want to keep it)
    • the shortcut function containsNot for nullable values => you can use containsNot.nullableValues(...)
    • the assertion function toThrow() => better there is only toThrow {} this way you do not need to chose and IntelliJ can compelete the function quicker.
    • the assertion function isSame and isNotSamein favour of isSameAs and isNotSameAs
    • the sequence inGiven order but only in API cc-infix-en_UK => use inGiven order and only
    • the functions object and objects of the sophisticated contains assertion builder for Iterable => use value, values respectively.
    • the assertion functions concerning Translatable => was most likely only used by Atrium internally. Is no longer used thus will be removed. But in any case, there is no need to pollute the API with functions most users do not use.
    • ReporterBuilder in package ch.tutteli.atrium.reporting => moved to package ch.tutteli.atrium.domain.builders (to be JDK9 ready where split package is not allowed), yet its inaccessible, use reporterBuilder instead
    • IndentAssertionGroup together with IndentAssertionGroupType and TextIndentAssertionGroupFormatter => please let me know (via slack or by opening an issue) if you used one of it. I happily show how you can achieve the same but differently.
    • InvisibleAssertionGroup -> use AssertImpl.builder.invisible instead.
    • AssertionGroup.Builder -> use AssertImpl.builder (one of the suggested replacements).
    • IAtriumFactory and AtriumFactory in favour of ICoreFactory, CoreFactory respectively -> use the suggested replacement.
    • DescriptiveAssertion::expected use DescriptiveAssertion::representation instead.
    • the whole module atrium-assertions -> use the atrium-domain modules instead
    • the whole package assertions.charsequence.builders as well as assertions.iterables.builders
    • the assertion verbs no longer reside in an own package but were moved to ch.tutteli.atrium.verbs because assert is a reserved keyword in Java and I would not have been able to define module-info.java where I export package assert

    The following was deprecated and will be made internal with 1.0.0:

    • BasicDescriptiveAssertion -> use AssertImpl.builder.descriptive instead
    • BasicExplanatoryAssertion -> use AssertImpl.builder.explanatory instead
    • BasicAssertionGroup -> use AssertImpl.builder
    • ExplanatoryAssertionGroup -> use AssertImpl.builder.explanatoryGroup
    • EmptyNameAndSubjectAssertionGroup -> use AssertImpl.builder
    • ...CheckerOptionImpl (new classes - have to be public to retain backward compatibility - do not start to rely on them, rely on the interfaces instead)

    Possible Breaking Changes with 0.8.0

    • I might rename parameters. I assume that no one is using named parameters in the context of postulating assertions. Please let me know if I am wrong.

    Possible Breaking Changes with 1.0.0

    Please open an issue if you are not happy with one of the changes and state why or contact me via the Atrium slack channel.

    • returnValueOf functions might be renamed to returnValueOfX where X denotes the number of arguments. Too often it occurs that Kotlin is not able to infer the correct overload, the user does not get the appropriate help in code completion or the error message is too big. This should help.
    • feature assertion functions might require a lambda in the future. This way error reporting does not blow up in the middle of the way because subject is not available. However, there is a bug concerning nullable-features in Kotlin which prevents me from doing it at the moment: https://youtrack.jetbrains.com/issue/KT-23768, please up-vote it.
    • A type parameter might be added to AssertionGroup to restrict the AssertionGroupType.
    • BulletPointIdentifier together with subtypes (AssertionGroupTypes) might be moved to another package: ch.tutteli.atrium.reporting.assertions

    Migrating deprecated functionality

    You have time to replace the deprecated functionality with the new solutions until 1.0.0 -- enough time I guess, I don't even know what 1.0.0 will contain exactly (surely html reporting and multi platform functionality, that might take a while).

    I have placed corresponding ReplaceWith into most of the @Deprecated annotations. Unfortunately they do not all behave as I wish due to several bugs in this area (for instance: https://youtrack.jetbrains.com/issue/KT-24181, https://youtrack.jetbrains.com/issue/KT-10094)

    Please file a bug if I have forgotten to place a replacement somewhere or if it does not work as expected (e.g., if I have forgotten to add an import). I gladly fix it and provide a patch version => better than you have to adjust things manually all over the place :wink:.

    The biggest deprecation is due to my silly mistake of using en_UK instead of en_GB (thanks to @robfletcher for pointing it out). Luckily though, it gave me the opportunity to provide an API which is deprecation free and JDK9 ready. That means, once you have transformed all your deprecated code, you can switch from the en_UK bundle to the en_GB bundle and if it still compiles then you can be sure, that you did not miss to migrate a deprecated entity and you are Atrium 1.0.0 compatible (in case you are using a de_CH bundle: müsst euch bis 1.0.0 gedulden, dann wird auch de_CH deprecation free und JDK9 ready sein). Unfortunately, Intellij makes some replacements not terribly well, due to the following bug https://youtrack.jetbrains.com/issue/KT-10094. As far as I have seen it only concerns consumers of the infix API (guidance is given by the following checklist).

    Following a checklist which shall help you migrating things (I have done the same for migrating atrium-spec to use cc-en_GB instead of cc-en_UK). You only need to consider the checklist for the bundle you use:

    atrium-cc-en_UK-robstoll

    Please make sure you have enabled the search options "Match case" and "Regex" in the following search & replace instructions.

    • [ ] update dependency to ch.tutteli.atrium:atrium-cc-en_UK-robstoll:0.7.0 (notice groupId has changed to ch.tutteli.atrium)

    • [ ] if you use a predefined verb:

      • verbs are no longer in an own package:
      1. search for: import ch.tutteli.atrium.verbs.(?:assert.(assert)|assertthat.(assertThat)|expect.(expect))
      2. replace with: import ch.tutteli.atrium.verbs.$1$2$3
    • [ ] search for: .(toThrow<[^>]+>)\(\) replace with: .$1\{\}

    Either you replace the deprecated functions using IntelliJ's function "replace with suggestion", or you can use the following approach which might need some manual adjustment in the end but not much I guess. Yet, I think you are still better of using this approach over "replace with suggestion" due to the following bug: https://youtrack.jetbrains.com/issue/KT-24181 which makes replacements not ideal.

    • [ ] search for: ReporterBuilder
      => in case you have a match:

      • replace buildOnlyFailureReporter with the suggestion (ALT + Enter -> Replace with ...) => the code will not immediately compile, go on with the next step (at the end it will)
      • replace other deprecated functions starting from the bottom => at the end you will have replaced ReporterBuilder by reporterBuilder
      • remove the import ch.tutteli.atrium.reporting.ReporterBuilder
    • in case you have defined your own assertion verbs
      Configuration of the reporter can be done via verb directly but it is advisable to do it via ReporterFactory. In case you do not use another configuration than the DefaultReporterFactory already defines, then it is enough if you just exchange your Reporter with reporter (add import ch.tutteli.atrium.reporting.reporter) In case your configuration differs then you have to specify your own ReporterFactroy. See atriumVerbs.kt of API de_CH for an example how you define one. Furthermore, you need register your own ReporterFactory as service (ServiceLoader mechanism, see how it is done in API de_CH).

    • [ ] search for: (import ch.*)en_UK(.*)
      replace with: $1en_GB$2

    • [ ] search for: isTrue\(\)
      replace with: toBe(true)

    • [ ] search for: isFalse\(\)
      replace with: toBe(false)

    • [ ] search for: import ch.*en_GB.is(?:True|False)([\S\s]*)(import ch.*en_GB).is(?:True|False)
      replace with: $1$2.toBe

    • [ ] search for: isNull\(\)
      replace with: toBe(null)

    • [ ] remove duplicated toBe imports, you might have to replace multiple times:
      search for: (import ch.*en_GB.toBe)([\S\s]*)import ch.*en_GB.toBe
      replace with: $1$2

    • [ ] search for: isNotNull
      replace with: notToBeNull

    • [ ] search for: isSame
      replace with: isSameAs

    • [ ] search for: isNotSame
      replace with: isNotSameAs

    • [ ] search for: ((?:inAnyOrder\.(?:only\.)?)|(?:inOrder\.only\.))`object`
      replace with: $1value

    • [ ] search for: ((?:inAnyOrder\.(?:only\.)?)|(?:inOrder\.only\.))objects
      replace with: $1values

    • [ ] You might have some errors in conjunction with Iterable with nullable elements (Iterable<T?>) which you need to fix manually. There are replacements like nullableValue instead of value etc.

    • [ ] compile and see if you have deprecation warnings if so, fix them by using the suggested replacements

    • [ ] to test if you have removed all deprecated function you can adjust your gradle dependency and depend on atrium-cc-en_GB-robstoll instead of atrium-cc-en_UK-robstoll

    • [ ] remove the dependency to atrium-verbs in case you have defined it (is already defined in the bundle atrium-cc-en_GB-robstoll)

    Ping me in the Atrium slack channel if you need help.

    atrium-cc-infic-en_UK-robstoll

    Please make sure you have enabled the search options "Match case" and "Regex" in the following search & replace instructions.

    • [ ] update dependency to Atrium as follows:

      buildscript {
          ext { atrium_version='0.7.0' }
      }
      repositories {
          jcenter()
          // either use jcenter or the repository on the next line
          // maven { url "http://dl.bintray.com/robstoll/tutteli-jars" }
      }
      dependencies {
          testCompile("ch.tutteli.atrium:atrium-cc-infix-en_GB-robstoll:$atrium_version") { 
              exclude group: 'ch.tutteli.atrium', module: 'atrium-api-cc-en_GB' 
          }
          testRuntimeOnly("ch.tutteli.atrium:atrium-api-cc-en_GB-robstoll:$atrium_version")
      }
      

      We have to define an exclude due to a missing feature (or you could call it a bug) so that maven dependencies defined with <scope>runtime</scope> are treated as compile nonetheless. If you are using gradle > 4.6, then you can put enableFeaturePreview("IMPROVED_POM_SUPPORT") in your settings.gradle and simplify the dependencies section to the following:

      dependencies {
          testCompile "ch.tutteli.atrium:atrium-cc-infix-en_GB-robstoll:$atrium_version" 
      }
      
    • [ ] if you use a predefined verb:

      • verbs are no longer in an own package:
      1. search for: import ch.tutteli.atrium.verbs.(?:assert.(assert)|assertthat.(assertThat)|expect.(expect))
      2. replace with: import ch.tutteli.atrium.verbs.$1$2$3
    • [ ] search for: .(toThrow<[^>]+>)\(\) replace with: .$1\{\}

    • [ ] Objects was deprecated in favour of Values -- I have not placed a ReplaceWith into the @Deprecated annotation due to mentioned bug.

      1. search for: Objects
      2. replace with: Values
      3. search for: Values(Values
      4. replace with: Values
    • [ ] search for: ch.tutteli.atrium.api.cc.infix.en_UK.(Empty|contain|case|only|order)
      replace with: ch.tutteli.atrium.api.cc.infix.en_GB.keywords.$1
      => this might not be as successful as I wish due to star import, you probably have to do some adjustments manually

    Either you replace the deprecated functions using IntelliJ's function "replace with suggestion", or you can use the following approach which might need some manual adjustment in the end but not much I guess. Yet, I think you are still better of using this approach over "replace with suggestion" due to the following bug: https://youtrack.jetbrains.com/issue/KT-24181 which makes replacements not ideal.

    • [ ] search for: ReporterBuilder
      => in case you have a match:

      • see if you are using the deprecated fun withTranslationSupplier and replace it with the suggested replacement (ALT + Enter -> Replace with ...)
      • exchange buildOnlyFailureReporter() with .onlyFailureReporter().build()
      • remove the import ch.tutteli.atrium.reporting.ReporterBuilder
    • in case you have defined your own assertion verbs
      Configuration of the reporter can be done via verb directly but it is advisable to do it via ReporterFactory. In case you do not use another configuration than the DefaultReporterFactory already defines, then it is enough if you just exchange your Reporter with reporter (add import ch.tutteli.atrium.reporting.reporter) In case your configuration differs then you have to specify your own ReporterFactroy. See [atriumVerbs.kt of API de_CH)(https://github.com/robstoll/atrium/tree/v0.7.0/apis/atrium-api-cc-de_CH/src/test/kotlin/ch/tutteli/atrium/atriumVerbs.kt) for an example how you define one. Furthermore, you need register your own ReporterFactory as service (ServiceLoader mechanism, see how it is done in API de_CH).

    • [ ] search for: (import ch.*)en_UK(.*)
      replace with: $1en_GB$2

    • [ ] search for: isSame
      replace with: isSameAs

    • [ ] search for: isNotSame
      replace with: isNotSameAs

    • [ ] search for: ((?:inAny order (?:but only)?)|(?:inGiven order but only) )object
      replace with: $1value

    • [ ] search for: inGiven order but only replace with: inGiven order and only

    • [ ] You might have some errors in conjunction with Iterable with nullable elements (Iterable<T?>) which you need to fix manually. There are replacements like nullableValue instead of value etc.

    • [ ] to test if you have removed all deprecated function you can adjust your gradle dependency and depend on atrium-cc-infic-en_GB-robstoll instead of atrium-cc-infic-en_UK-robstoll

    • [ ] remove the dependency to atrium-verbs in case you have defined it (is already defined in the bundle atrium-cc-infic-en_GB-robstoll)

    Ping me in the Atrium slack channel if you need help.

    atrium-cc-de_CH-robstoll

    • [ ] update dependency to Atrium as follows:

      buildscript {
          ext { atrium_version='0.7.0' }
      }
      repositories {
          jcenter()
          // either use jcenter or the repository on the next line
          // maven { url "http://dl.bintray.com/robstoll/tutteli-jars" }
      }
      dependencies {
          testCompile("ch.tutteli.atrium:atrium-cc-de_CH-robstoll:$atrium_version") { 
              exclude group: 'ch.tutteli.atrium', module: 'atrium-api-cc-en_GB' 
          }
          testRuntimeOnly("ch.tutteli.atrium:atrium-api-cc-en_GB-robstoll:$atrium_version")
      }
      

      We have to define an exclude due to a missing feature (or you could call it a bug) so that maven dependencies defined with <scope>runtime</scope> are treated as compile nonetheless. If you are using gradle > 4.6, then you can put enableFeaturePreview("IMPROVED_POM_SUPPORT") in your settings.gradle and simplify the dependencies section to the following:

      dependencies {
          testCompile "ch.tutteli.atrium:atrium-cc-de_CH-robstoll:$atrium_version" 
      }
      
    • [ ] if you use a predefined verb:

      • verbs are no longer in an own package:
      1. search for: import ch.tutteli.atrium.verbs.(?:assert.(assert)|assertthat.(assertThat)|expect.(expect))
      2. replace with: import ch.tutteli.atrium.verbs.$1$2$3
    • [ ] search for: .(toThrow<[^>]+>)\(\) replace with: .$1\{\}

    • [ ] search for: ReporterBuilder
      => in case you have a match:

      • replace buildOnlyFailureReporter with the suggestion (ALT + Enter -> Replace with ...) => the code will not immediately compile, go on with the next step (at the end it will)
      • replace other deprecated functions starting from the bottom => at the end you will have replaced ReporterBuilder by reporterBuilder
      • remove the import ch.tutteli.atrium.reporting.ReporterBuilder
    • [ ] use the suggested replacements for deprecated functions.

    • [ ] remove the dependency to atrium-verbs in case you have defined it (is already defined in the bundle atrium-cc-de_CH-robstoll)

    Ping me in the Atrium slack channel if you need help.

    Not included in this release yet

    • sophisticated assertion builder for toBe with error tolerance for floating point numbers
    • failure hint for toBe in conjuction with Float or Double
    Source code(tar.gz)
    Source code(zip)
  • v0.7.0-RC2(Jan 19, 2019)

  • v0.7.0-RC1(Jan 19, 2019)

  • v0.6.0(Jul 20, 2018)

    Jar's can be found here: https://bintray.com/robstoll/tutteli-jars/atrium/0.6.0 API Maturity: Almost Stable Implementation Maturity: Development

    I provide more or less a stable API for users of Atrium. Only the ReporterBuilder, which you use to build your own assertion verb might slightly change its API (so migrating will be just a few lines in one file). There are most likely not any breaking changes in the API of the assertion functions/builders. However, I do not provide yet a stable API for the core of Atrium -- this might affect you as well if you write your own assertion functions. It also affects you if you provide your own implementation of the core of Atrium.

    New Features:

    • isNumericallyEqualTo and isNotNumericallyEqualTo for BigDecimal
    • isEqualIncludingScale and isNotEqualIncludingScale for BigDecimal => gives a hint in case of a failure where the assertion would have hold with isNumericallyEqualTo / isNotNumericallyEqualTo
    • overload of toBe for BigDecimal which is deprecated, throws an UnsupportedOperationException and points the user to the above functions
    • same same but different for the overload of notToBe for BigDecimal
    • toBeWithErrorTolerance for floating point numbers (Float, Double, BigDecimal)
    • isLessThan, isLessThanOrEquals etc. is now available for Comparable<T> and no longer only for Number
    • containsRegex(...) as shortcut for contains.atLeast(1).regex(...)
    • integrated contains not into the sophisticated contains assertion builder (for CharSequence and Iterable) => so that you see how many times it was contained when the assertion fails
    • identification lambdas for Iterable with nullable types. For instance, listOf(null, 1).contains { toBe(1) } was not possible so far (only List<Int> was supported)
    • made AssertionPairFormatter configurable, one can now chose for instance a multi-line formatter instead of same-line (see README#your-first-assertion for an example)
    • workaround for a Bug in Kotlin (please upvote it) which causes that returnValueOf cannot be used for methods with overloads.
    • I generalised the DownCaster to a TypeTransformer which you can reuse by using the impl-function _typeTransformation. You find an example for Either in TypeTransformerSpec.
    • I added a few issues with label help wanted so that you get an easy start to contribute to Atrium.

    Breaking Changes:

    • removed deprecated ReporterBuilder::withDetailedObjectFormatter

    Deprecation:

    • ReporterBuilder::withSameLineTextAssertionFormatter -> use the suggested replacement

    Not included in this release yet

    • sophisticated assertion builder for toBe with error tolerance for floating point numbers
    • failure hint for toBe in conjuction with Float or Double
    • simplify the creation of failure hints, make it reusable for users
    Source code(tar.gz)
    Source code(zip)
  • v0.5.0(Jul 20, 2018)

    Jar's can be found here: https://bintray.com/robstoll/tutteli-jars/atrium/0.5.0 API Maturity: Almost Stable Implementation Maturity: Development

    I provide more or less a stable API for users of Atrium. Only the ReporterBuilder, which you use to build your own assertion verb might slightly change its API (so migrating will be just a few lines in one file). There are most likely not any breaking changes in the API of the assertion functions/builders. However, I do not provide yet a stable API for the core of Atrium -- this might affect you if you write your own assertion functions. It also affects you if you provide your own implementation of the core of Atrium.

    New Features:

    • added an infix API including a corresponding bundle module: atrium-cc-infix-en_UK-robstoll
    • one can use Assert<T> as entry point for assertion functions instead of AssertionPlant<T>.
    • containsDefaultTranslationOf is now also available in the sophisticated assertion builder for contains assertions for CharSequence

    Breaking Changes:

    • it as alias for subject was removed, due to legitimate confusion (see #7). In case you already use it, then you can re-add it yourself, paste the following into your assertionVerb.kt: val <T : Any> IAssertionPlant<T>.it get() : T = subject However, I recommend you rename it to x or something similar to avoid the confusion that it is actually not an implicit lambda parameter.

    • The assertion function contains for CharSequence checks now that only CharSequence, Number and Char are passed (at runtime). I consider it as too risky (prone to bugs) if one can pass Anything -- well compile time still requires only Any; that's because Kotlin does not (yet) support union types. IMO it is not possible to define more restrictive types (e.g. with multiple overloads) which does not hinder usability and maintanability. That's the reason why it still expects Any. If someone misses the functionality that one can pass in any object, then please open a feature request.

    • atrium-api-code-completion-en_UK was renamed to atrium-api-cc-en_UK, same for atrium-api-code-completion-de_CH. In case you have not used the bunde module atrium-cc-en_UK-robstoll but the API directly, then you need to modify your build.gradle

    • Interfaces are no longer prefixed with I with the exception of IAtriumFactory

    • Simplified the implementation for LocaleOrderDecider -> special cases Norwegian and Chinese.

      • no_NO_NY, no_NO etc. are no longer supported (not the recommended way anyway) -> use nb_... or nn_... instead
      • Locale zh_Hant without specifying the country is no longer supported (not the recommended way anyway)
      • fixed problems loading properties files for Locale with script (e.g. zh_Hant_TW)
      • made ResourceBundleBasedTranslator internal1 -- if you are using ReporterBuilder.withoutTranslation() or ReporterBuilder.withDefaultTranslator(...) respectively, then you don't have to do anything. In case you used ResourceBundleBasedTranslator, then use the mentioned methods instead.

    1 ResourceBundleBasedTranslator is just a reference implementation to assure that TranslationSupplierBasedTranslator is compatibel with ResourceBundle. Since it was compatible so far, one could have used ResourceBundleBasedTranslator instead of TranslationSupplierBasedTranslator as well. However, because I detected that there are bugs in the implementation of ResourceBundle in JDK8 I decided that ResourceBundleBasedTranslator should no longe be visible to users (bugs are only concerning the special cases Norwegian and Chinese but maybe there are more).

    Deprecation:

    • ReporterBuilder::withDetailedObjectFormatter will be removed in 0.6.0

    Not included in this release yet

    • overload for Iterable<T> contains assertions which support identification lambdas for nullable types.
    Source code(tar.gz)
    Source code(zip)
  • v0.4.0(Jul 20, 2018)

    Jar's can be found here: https://bintray.com/robstoll/tutteli-jars/atrium/0.4.0 API Maturity: Almost Stable Implementation Maturity: Development

    I provide more or less a stable API for users of Atrium. Only the ReporterBuilder, which you use only to build your own assertion verb might slightly change its API (so migrating will be just a few lines in one file). There won't be any changes in the API of the assertion functions/builders. However, I do not provide yet a stable API for the core of Atrium -- this might affect you if you write your own assertion functions as well as when you provide your own implementation of the core of Atrium.

    Included in this release:

    Functions to create single assertions and assertion groups.

    assertion functions and builders

    Components

    • Translator including a PropertiesBasedTranslationSupplier
    • OnlyFailureReporting
    • TextSameLineAssertionFormatter
    • DetailedObjectFormatter
    • ArgumentFormatter

    Improvements compared to 0.3.0

    • splitting of API and implementation; which enables to define multiple APIs
    • feature assertion builders for collection/iterable
    • introduction of explanatory assertion groups
    • fixes for IAssertionPlantCheckingLazily -> simplified, introduce ReportingPlant as a replacement
    • improved README, better explained examples

    Not included in this release yet

    • overload for Iterable<T> contains assertions which support identification lambdas for nullable types.
    Source code(tar.gz)
    Source code(zip)
  • v0.3.0(Jul 20, 2018)

    Jar's can be found here: https://bintray.com/robstoll/tutteli-jars/atrium/0.3.0 API Maturity: Almost Stable Implementation Maturity: Development (I provide a more or less a stable API for users of Atrium but not yet for implementers of atrium)

    Included in this release:

    assertion functions

    Components

    • Translator including a PropertiesBasedTranslationSupplier
    • OnlyFailureReporting
    • SameLineAssertionFormatter
    • DetailedObjectFormatter
    • ArgumentFormatter

    Improvements compared to 0.2.0

    • i18n support for assertions and assertion verbs
    • feature assertion for methods (0.2.0 had only feature assertions for properties)
    • a few more assertion functions
    • improved formatting in DetailObjectFormatter (boolean, enums, and KClass are treated specially)
    • further improved Code-Documentation incl. a better readable layout (see KDoc)

    Not included in this release yet

    • basic assertion functions for collections (containsOnly, contains, containsNot, containsInOrder etc.)
    Source code(tar.gz)
    Source code(zip)
  • v0.2.0(Jul 20, 2018)

    Jar's can be found here: https://bintray.com/robstoll/tutteli-jars/atrium/0.2.0 Maturity: Development

    Included in this release:

    • lazily and immediately evaluated assertions
    • assertion for nullable types
    • assertions about thrown exceptions
    • feature assertions for properties (functions should follow with Kotlin 1.1.2 assuming they fixed the reported bug)
    • generich check for properties (functions should follow with Kotlin 1.1.2 assuming they fixed the reported bug)
    • OnlyFailureReporting
    • SameLineAssertionFormatter
    • DetailedObjectFormatter
    • and a minimal set of assertion functions

    Improvements compared to 0.1.0

    • cleaner code design (separation of creation, checking and reporting)
    • improved Code-Documentation (KDoc available on gh-pages)
    • Proper README
    • Separation of API and implementation (atrium-impl-robstoll could be exchanged by another implementation)
    • Spec for atrium in form of tests which can be reused by other implementation

    Not included in this release yet:

    • basic assertion functions for collections (containsOnly, contains, containsNot, containsInOrder etc.)
    • support for multi-language assertions
    Source code(tar.gz)
    Source code(zip)
  • v0.1.0(Jul 20, 2018)

    A minimal version of assertK including:

    • assertion verb "assert"
    • lazy and immediate assertions
    • assertion for nullable types
    • feature assertions for properties (functions will follow)
    • except{} for Throwable
    • OnlyFailureReporting
    • SameLineAssertionMessageFormatter
    • and a minimal set of assertion functions

    Not included in this release yet:

    • a not operator
    • assertion functions for collections
    • assertion functions in form of operators
    Source code(tar.gz)
    Source code(zip)
Selenium locators for Java/Kotlin that resemble the Testing Library (testing-library.com).

Selenium Testing Library Testing Library selectors available as Selenium locators for Kotlin/Java. Why? When I use Selenium, I don't want to depend on

Luís Soares 5 Dec 15, 2022
mocking library for Kotlin

Kotlin Academy articles Check the series of articles "Mocking is not rocket science" at Kt. Academy describing MockK from the very basics of mocking u

MockK 4.8k Jan 3, 2023
A Kotlin Android library for heuristics evasion that prevents your code from being tested.

EvadeMe An Android library for heuristics evasion that prevents your code from being tested. User Instructions Add the maven repository to your projec

Chris Basinger 29 Dec 26, 2022
Kotlin wrapper for React Test Renderer, which can be used to unit test React components in a Kotlin/JS project.

Kotlin API for React Test Renderer Kotlin wrapper for React Test Renderer, which can be used to unit test React components in a Kotlin/JS project. How

Xavier Cho 7 Jun 8, 2022
Android library that allows you to run your acceptance tests written in Gherkin in your Android instrumentation tests.

Green Coffee Green Coffee is a library that allows you to run your acceptance tests written in Gherkin in your Android instrumentation tests using the

Mauricio Togneri 227 Nov 21, 2022
Linkester is an Android library that aims to help Android developers test their deep links implementation.

Linkester Linkester is an Android library that aims to help Android developers test their deep links implementation. The idea is to have a new launche

Ahmad Melegy 79 Dec 9, 2022
Jitpack Library Tester

jitpack-library-test Repository for testing build from jitpack.io Red : Failed Green : Success / Pass Colaborator Very open to anyone, I'll write your

Faisal Amir 7 Dec 10, 2022
Turbine is a small testing library for kotlinx.coroutines Flow.

A small testing library for kotlinx.coroutines Flow

Cash App 1.8k Jan 5, 2023
A library that makes it easier to write high quality automated acceptance tests

Getting started with Serenity and Cucumber Serenity BDD is a library that makes it easier to write high quality automated acceptance tests, with power

ricardo larrahondo 1 Oct 20, 2021
An experimental library for dealing with legacy code

All Super experimental! no guarantees to the public API surface. At the moment, this project is the outcome of the limitations of my search skills. Bu

Ragunath Jawahar 5 Jan 18, 2022
Library to simplify and speed up the creation and work with adapters with payload.

Novalles Library to simplify and speed up the creation and work with adapters with payload. How to use Annotate your UI model with UIModel Annotation.

Бырна Алексей 3 Oct 11, 2022
Portable validations for Kotlin

Portable validations for Kotlin ✅ Type-safe DSL ?? Multi-platform support (JVM, JS) ?? Zero dependencies Installation For multiplatform projects: kotl

null 509 Dec 18, 2022
Powerful, elegant and flexible test framework for Kotlin with additional assertions, property testing and data driven testing

Kotest is a flexible and comprehensive testing tool for Kotlin with multiplatform support. To learn more about Kotest, visit kotest.io or see our quic

Kotest 3.8k Jan 3, 2023
Fixtures for Kotlin providing generated values for unit testing

A tool to generate well-defined, but essentially random, input following the idea of constrained non-determinism.

Appmattus Limited 191 Dec 21, 2022
Lightweight service for creating standalone mock, written in pure Kotlin with Netty container.

MockService The lightweight service for creating a standalone mock, written in pure Kotlin with Netty container. The service allows getting config fil

null 2 Oct 28, 2021
Proyecto de Kotlin y JPA sobre Hibernate, con algunos test usando JUnit 5 y Mockito.

Contactos Kotlin JPA Ejemplos de una aplicación de manejo de contactos con Kotlin y JPA. Usando para testear la aplicación JUnit 5 y Mockito. Almacena

José Luis González Sánchez 3 Sep 13, 2022
This is a sample API project for Rest Assured with Maven Kotlin DSL & JUnit 5

Kotlin-RestAssured Test Automation This is a sample API project for Rest Assured with Maven Kotlin DSL & JUnit 5 Introduction All the Test Cases kept

Dilshan Fernando 0 Dec 9, 2021
Android Sample Kotlin+ MVVM + Coroutines + Retrofit +Hilt+ Junit + Mockito

NTimes AppSample NY Times Most Popular Articles simple app to hit the NY Times Most Popular Articles API and show a list of articles, that shows detai

Amer Elsayed 0 Dec 27, 2021
Snapshot Testing framework for Kotlin.

KotlinSnapshot Snapshot Testing framework for Kotlin. What is this? Snapshot testing is an assertion strategy based on the comparision of the instance

Pedro Gómez 157 Nov 13, 2022