Atrium is an open-source assertion library for Kotlin with a fluent API. The project was inspired by AssertJ at first (and was therefore named AssertK) but it moved on and provides now more flexibility and features to its users (so to you 😉).
It is designed to support different APIs, different reporting styles and Internationalization (i18n). The core of Atrium as well as the builders to create sophisticated assertions are designed to be extensible and thus allow you to extend or replace components easily.
Atrium currently provides two APIs focusing 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.
Table of Content
- Installation
- Examples
- Write own Assertion Functions
- Use own Assertion Verbs
- Internationalization
- APIs
- Contribute
- KDoc - Code Documentation
- Known Limitations
- FAQ
- Roadmap
- License
Atrium is linked to jcenter but can also be retrieved directly from bintray.
gradle:
buildscript {
ext { atrium_version='0.4.0' }
}
repositories {
jcenter()
}
dependencies {
testCompile "ch.tutteli:atrium-cc-en_UK-robstoll:$atrium_version"
}
maven:
Because maven is a bit more verbose than gradle, the example is not listed here but
an settings.xml
is provided to set up the repository as well as an
example pom.xml
which includes the necessary dependencies.
Next to specifying a dependency to a predefined API you have to setup your assertion verbs (recommended way) or use the predefined assertion verbs.
That is all, you are all set. The next section shows you how to use Atrium.
We start off with a simple example:
val x = 10
assert(x).toBe(9)
The statement can be read as "I assert, x to be nine" and since this is false an AssertionError
is thrown with the following message:
assert: 10 (java.lang.Integer <934275857>)
◆ to be: 9 (java.lang.Integer <1364913072>)
where ? ...
represents a single assertion for the subject (10
in the above example) of the assertion.
The examples in the following sections include the error message (the output) in the code example itself as comments.
Atrium lets you choose the assertion verb (assert
in the above example).
Regardless whether you prefer expect
, assertThat
or yet another assertion verb/phrase
you can define your own assertion verbs which suit your coding style.
In the following examples we will use assert
for regular assertions
and expect
to postulate that we Expect an Exception.
The next section shows you how you can define multiple assertions for the same subject.
// two single assertions
assert(4 + 6).isLessThan(5).isGreaterThan(10)
// assert: 10 (java.lang.Integer <1841396611>)
// ◆ is less than: 5 (java.lang.Integer <1577592551>)
Using the fluent API allows you to write the assert(...)
part only once but making 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.
So the first statement could also be written as follows (unless the expression determining the subject has side effects).
assert(4 + 6).isLessThan(5)
assert(4 + 6).isGreaterThan(10)
Correspondingly, the first assert
statement (which does not hold) throws an AssertionError
.
In the above example, isLessThan(5)
is already wrong and thus isGreaterThan(10)
was not evaluated at all.
If you want that both assertions are evaluated together, then use the assertion group syntax as follows:
// assertion group
assert(4 + 6) {
isLessThan(5)
isGreaterThan(10)
}
// assert: 10 (java.lang.Integer <1841396611>)
// ◆ is less than: 5 (java.lang.Integer <1577592551>)
// ◆ is greater than: 10 (java.lang.Integer <1841396611>)
An assertion group throws an AssertionError
at the end of its block; hence reports that both assertions do not hold.
val subtitle : String? = "postulating assertions made easy"
assert(subtitle).isNull()
// assert: "postulating assertions made easy" <22600334>
// ◆ to be: null
assert(subtitle).isNotNull{ startsWith("atrium") }
//assert: "postulating assertions made easy" <651100072>
//◆ starts with: "atrium" <222427158>
If the subject of the assertion has a nullable type then
you need to define first, whether you expect it to be null
or not.
In case you expect that it isNotNull
you can define one or more subsequent assertions
for the subject as if it had a non-nullable type (String
in the above example) by defining an
assertion group block
-- { startsWith("atrium") }
in the above example.
expect {
//this block does something but eventually...
throw IllegalArgumentException("name is empty")
}.toThrow<IllegalStateException>()
// expect the thrown exception: java.lang.IllegalArgumentException: name is empty (java.lang.IllegalArgumentException <1364913072>)
// ◆ is a: IllegalStateException (java.lang.IllegalStateException)
You can define an expect
block together with the function toThrow
to make the assertion that the block throws a certain exception
(IllegalStateException
in the example above).
Moreover, you can define one or more subsequent assertions in the same assertion statement with the help of an
assertion group block.
The subsequent assertions are evaluated in case the expected Throwable
is thrown and is of the same type as the expected one (or a subtype).
For instance:
expect {
throw IllegalArgumentException("name is empty")
}.toThrow<IllegalArgumentException> {
message { startsWith("firstName") }
}
// expect the thrown exception: java.lang.IllegalArgumentException: name is empty (java.lang.IllegalArgumentException <371800738>)
// ◆ ▶ message: "name is empty" <1364767791>
// ◾ starts with: "firstName" <1499136125>
Notice message
in the
assertion group block
is a shortcut for property(subject::message).isNotNull()
, which creates a property assertion (see next section)
about Throwable::message
.
data class Person(val name: String, val isStudent: Boolean)
val myPerson = Person("Robert", false)
assert(myPerson) {
property(subject::name).toBe("Peter")
property(subject::isStudent).isTrue()
}
// assert: Person(name=Robert, isStudent=false) (Person <1841396611>)
// ◆ ▶ name: "Robert" <1577592551>
// ◾ to be: "Peter" <854587510>
// ◆ ▶ isStudent: false
// ◾ to be: true
You can also make assertions about one or several properties of the subject using property
in an assertion group block
-- general speaking, it allows you to create feature assertions without the need of own assertion functions.
In the above example, subject
within the assertion group block refers to myPerson
.
So we created two feature assertions: one for the property name
and the other for the property isStudent
of myPerson
.
A feature assertion is indicated as follows in the output. It starts with a ?
followed by the feature's name and its actual value.
So the above output can be read as "I assert, Person's name (which is actually "Robert"
) to be "Peter"
and its property isStudent
(which is actually false
) to be true
".
ℹ️ You can also use it
as alternative for subject
. The above could have been written as:
assert(person) {
property(it::name).toBe("Peter")
property(it::isStudent).isTrue()
}
message
shown in the example
Expect an Exception and whether it is better to write own assertion functions or use property
:
The only drawback IMO 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 I did with message
).
Yet, I do not recommend to write an own assertion function for every single property, 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 and the assertion function in sync.
Hence, only write an own assertion function in case you use it a lot and you need to apply an isNotNull
assertion first
-- which is the case when the property has a nullable type (as it is the case for Throwable::message
, see Nullable Types for further information).
In such a case I would find it too cumbersome to write property(subject::xY).isNotNull { ... }
all the time and would prefer xY { ... }
and make some extra effort when the property is renamed.
data class Person(val firstName: String, val lastName: String) {
fun fullName() = "$firstName $lastName"
fun nickname(includeLastName: Boolean) = when(includeLastName){
false -> "Mr. $firstName"
true -> "$firstName aka. $lastName"
}
}
val person = Person("Robert", "Stoll")
assert(person) {
returnValueOf(subject::fullName).contains("treboR", "llotS")
returnValueOf(subject::nickname, false).toBe("Robert aka. Stoll")
}
// assert: Person(firstName=Robert, lastName=Stoll) (ch.tutteli.atrium.api.cc.en_UK.IterableContainsInOrderOnlyEntriesSpec$1$2$Person <168907708>)
// ◆ ▶ fullName(): "Robert Stoll" <447718425>
// ◾ contains: "treboR" <1206569586>
// ⚬ ▶ number of occurrences: 0
// ◾ is at least: 1
// ◾ contains: "llotS" <1427381743>
// ⚬ ▶ number of occurrences: 0
// ◾ is at least: 1
// ◆ ▶ nickname(false): "Mr. Robert" <1427646530>
// ◾ to be: "Robert aka. Stoll" <846254484>
You can also make an assertion about a method of the subject or rather about the value which is returned when calling the method with some specified arguments.
Such feature assertions can be made with the help of the assertion function returnValueOf
.
There are overloads to support methods with up to 5 parameters (notice, fullName
has none and nickname
has one parameter in the above example).
The error message shows also another nice feature of Atrium.
It provides builders to create more sophisticated assertions.
Using contains("treboR", "llotS")
is actually a shortcut for calling an sophisticated assertion builder for CharSequence
.
In this example it calls contains.atLeast(1).values("treboR", "llotS")
which is reflected in the output.
Have a look at the KDoc of the CharSequence contains Builders
to see more options.
ℹ️ You can use it
as alternative for subject
as you can in Property Assertions.
Atrium provides assertion builders which allow to make sophisticated contains
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 one of the
Shortcut Functions in case you have kind of a common case.
The following sub sections show both use cases by examples.
assert(listOf(1, 2, 2, 4)).contains(2, 3)
// assert: [1, 2, 2, 4] (java.util.Arrays$ArrayList <1448525331>)
// ◆ contains, in any order: 3 (java.lang.Integer <1108924067>)
// ⚬ ▶ number of occurrences: 0
// ◾ is at least: 1
The assertion function contains(2, 3)
is a shortcut for using a
Sophisticated Assertion Builder -- it actually calls contains.inAnyOrder.atLeast(1).values(2, 3)
.
This is reflected in the output, which tells us that we expected that the number of occurrences
of 3
(which is actually 0
) is at least: 1
.
And 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 we defined an
Only Failure Reporter
which shows us only assertions (or sub assertions) which failed.
Back to the shortcut functions Atrium provides for common contains
assertions.
Next to expecting that certain values (or objects) are contained in or rather returned by an Iterable
,
Atrium allows us to write identification lambdas in form of assertion group blocks.
An entry is considered as identified if it holds all specified assertions of such a block.
Following an example:
assert(listOf(1, 2, 2, 4)).contains({ isLessThan(0) }, { isGreaterThan(2); isLessThan(4) })
// assert: [1, 2, 2, 4] (java.util.Arrays$ArrayList <1144068272>)
// ◆ contains, in any order:
// ⚬ an entry which:
// » is less than: 0 (java.lang.Integer <1985836631>)
// ⚬ ▶ number of occurrences: 0
// ◾ is at least: 1
// ⚬ an entry which:
// » is greater than: 2 (java.lang.Integer <1948471365>)
// » is less than: 4 (java.lang.Integer <1636506029>)
// ⚬ ▶ number of occurrences: 0
// ◾ is at least: 1
In the above example neither of the two identification lambdas matched any entries and thus both are reported as failing (sub) assertions.
The last two contains
shortcut functions which Atrium provides for Iterable<T>
are kind of the opposite of inAnyOrder.atLeast(1)
and are named containsStrictly
.
Again Atrium provides two overloads, one for values/objects, e.g. containsStrictly(1, 2)
which calls contains.inOrder.only.values(1, 2)
and
a second one which expects one or more identification lambdas, e.g. containsStriclty({ isLessThan(0) }, { isGreaterThan(5) })
and effectively calls contains.inOrder.only.entries({ isLessThan(2) }, { isGreaterThan(5) })
.
We will spare the examples here and show them in the following sections.
The sophisticated assertion builders Atrium provides, implement a fluent builder pattern.
To use the assertion builder for sophisticated Iterable<T>
-contains-assertions, you can type contains
-- as you would when using the Shortcut Functions --
but type .
as next step (thus you are using the property contains
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; the fluent builders will guide you through your decision making
Following on the last section we will start with an inOrder
example:
assert(listOf(1, 2, 2, 4)).contains.inOrder.only.entries({ isLessThan(3) }, { isLessThan(2) })
// assert: [1, 2, 2, 4] (java.util.Arrays$ArrayList <817978763>)
// ◆ contains only, in order:
// ✔ ▶ entry 0: 1 (java.lang.Integer <1578009262>)
// ◾ an entry which:
// ⚬ is less than: 3 (java.lang.Integer <1108924067>)
// ✘ ▶ entry 1: 2 (java.lang.Integer <1948471365>)
// ◾ an entry which:
// ⚬ is less than: 2 (java.lang.Integer <1948471365>)
// ✘ ▶ size: 4
// ◾ to be: 2
// ❗❗ additional entries detected:
// ⚬ entry 2: 2 (java.lang.Integer <1948471365>)
// ⚬ entry 3: 4 (java.lang.Integer <1636506029>)
Since we have chosen the only
option, Atrium shows us a summary where we see three things:
- Whether a specified identification lambda matched (signified by
?
or?
) the corresponding entry or not (e.g.? ? entry 1:
was2
and we expected, itis less than 2
) - Whether the expected size was correct or not (
? size:
was4
, we expected it,to be: 2
-- see also Property Assertions) - and last but not least, mismatches or additional entries as further clue (
?? additional entries detected
).
😍 I am pretty sure you are going to love this feature as well
-- in case you are dealing with large Iterable
and do not want such a verbose output,
then let me know it by writing a feature request.
Also notice, that Atrium cannot yet deal with infinite Iterable
s.
Following one more example for inOrder
as well as a few examples for inAnyOrder
.
I 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-kotlin Slack channel
and I happily answer your question there.
assert(listOf(1, 2, 2, 4)).contains.inOrder.only.values(1, 2, 2, 3, 4)
// assert: [1, 2, 2, 4] (java.util.Arrays$ArrayList <1362728240>)
// ◆ contains only, in order:
// ✔ ▶ entry 0: 1 (java.lang.Integer <1578009262>)
// ◾ to be: 1 (java.lang.Integer <1578009262>)
// ✔ ▶ entry 1: 2 (java.lang.Integer <1948471365>)
// ◾ to be: 2 (java.lang.Integer <1948471365>)
// ✔ ▶ entry 2: 2 (java.lang.Integer <1948471365>)
// ◾ to be: 2 (java.lang.Integer <1948471365>)
// ✘ ▶ entry 3: 4 (java.lang.Integer <1636506029>)
// ◾ to be: 3 (java.lang.Integer <1108924067>)
// ✘ ▶ entry 4: ❗❗ hasNext() returned false
// ◾ to be: 4 (java.lang.Integer <1636506029>)
// ✘ ▶ size: 4
// ◾ to be: 5
assert(listOf(1, 2, 2, 4)).contains.inAnyOrder.atLeast(1).butAtMost(2).entries({ isLessThan(3) })
// assert: [1, 2, 2, 4] (java.util.Arrays$ArrayList <1092572064>)
// ◆ contains, in any order:
// ⚬ an entry which:
// » is less than: 3 (java.lang.Integer <1108924067>)
// ⚬ ▶ number of occurrences: 3
// ◾ is at most: 2
assert(listOf(1, 2, 2, 4)).contains.inAnyOrder.only.values(1, 2, 3, 4)
// assert: [1, 2, 2, 4] (java.util.Arrays$ArrayList <922511709>)
// ◆ contains only, in any order:
// ✔ an entry which is: 1 (java.lang.Integer <1578009262>)
// ✔ an entry which is: 2 (java.lang.Integer <1948471365>)
// ✘ an entry which is: 3 (java.lang.Integer <1108924067>)
// ✔ an entry which is: 4 (java.lang.Integer <1636506029>)
// ✔ ▶ size: 4
// ◾ to be: 4
// ❗❗ following entries were mismatched:
// ⚬ 2 (java.lang.Integer <1948471365>)
assert(listOf(1, 2, 2, 4)).contains.inAnyOrder.only.values(4, 3, 2, 2, 1)
// assert: [1, 2, 2, 4] (java.util.Arrays$ArrayList <331994761>)
// ◆ contains only, in any order:
// ✔ an entry which is: 4 (java.lang.Integer <1636506029>)
// ✘ an entry which is: 3 (java.lang.Integer <1108924067>)
// ✔ an entry which is: 2 (java.lang.Integer <1948471365>)
// ✔ an entry which is: 2 (java.lang.Integer <1948471365>)
// ✔ an entry which is: 1 (java.lang.Integer <1578009262>)
// ✘ ▶ size: 4
// ◾ to be: 5
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 the
specifications
for more examples.
A catalog of the available assertion functions
can be found in the code documentation.
Are you missing an assertion function for a specific type and the generic functions property and returnValueOf are not good enough?
Writing one is very simple and a pull request of your new assertion function is very welcome. Following an example:
fun IAssertionPlant<Int>.isMultipleOf(base: Int) = createAndAddAssertion(
Untranslatable("is multiple of"), base, { subject % base == 0 })
and its usage:
assert(12).isMultipleOf(5)
// assert: 12 (java.lang.Integer <934275857>)
// ◆ is multiple of: 5 (java.lang.Integer <1364913072>)
Let's see how we actually defined isMultipleOf
.
First of, you need to know that IAssertionPlant<T>
is the entry point for assertion functions.
We get an IAssertionPlant<Int>
when calling assert(12)
and an IAssertionPlant<String>
for assert("hello")
.
In our case we want to define the assertion function only for subject
s of type Int
hence we define isMultipleOf
as
extension function
of IAssertionPlant<Int>
.
We then use the method createAndAddAssertion
(which is provided by IAssertionPlant
) to create the assertion,
add it to the plant itself
and return the plant to support a fluent API.
The method createAndAddAssertion expects:
- an ITranslatable as description of your assertion.
- the representation of the expected value.
- and the actual check as lambda where you typically use the
subject
of the assertion.
We use an Untranslatable
as first argument here because we are not bothered with internationalization.
In case you want to report in a different language, then have a look at Internationalization.
Typically you use the expected value itself as its representation -- so you pass it as second argument.
But not all assertion functions require a value which is somehow compared against the subject -- some make an assertion about a property of a subject without comparing it against an expected value. Consider the following assertion function:
fun IAssertionPlant<Int>.isEven() = createAndAddAssertion(
DescriptionBasic.IS, RawString("an even number"), { subject % 2 == 0 })
We are using a RawString
here so that "an even number"
is not treated as a String
in reporting.
Also notice, that we are reusing a common description (DescriptionBasic.IS
) as first argument.
Its usage looks then as follows:
assert(13).isEven()
// assert: 13 (java.lang.Integer <1841396611>)
// ◆ is: an even number
Do you want to write an own sophisticated assertion builder instead of an assertion function?
Have a look at the implementation, for instance how the sophisticated assertion builders for Iterable<T>
are defined:
ch.tutteli.atrium.assertions.iterable.contains.
Notice that the implementation supports Internationalization.
If you have a question, then please post it in the
atrium-kotlin Slack channel
and I will try to help you.
Atrium offers three assertion verbs for the impatient: assert
, assertThat
and expect
.
However , I suggest that you use your own assertion verbs even in the case you name them
assert
, assertThat
or expect
. The benefit will be that you are able to change the
reporting style in the future without modifying existing test code.
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.
In order to create an own assertion verb it is sufficient to copy the file content of
atriumVerbs.kt
paste it in your own atriumVerbs.kt, rename assert
and expect
as desired and rename the package to reflect yours.
As you can see, it is up to you if you use the same name for all assertion functions or not
(Atrium itself uses expect
to postulate assertions about thrown Throwable
s and assert
for other assertions).
If you still insist of using the provided assertion verbs, then add the following dependency to your project in addition (see Installation for the rest of the gradle script).
gradle:
dependencies {
//... see other dependency in the example above
testCompile "ch.tutteli:atrium-verbs:$atrium_version"
}
maven:
Have a look at the example pom.xml.
We distinguish between to use cases. You might want to generate the Report in a different language or you might want to use the API in a different language.
Following on the example in Write own Assertion Functions we show here how you write the 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 an Untranslatable
but a proper
ITranslatable
.
fun IAssertionPlant<Int>.isMultipleOf(base: Int) = createAndAddAssertion(
DescriptionIntAssertions.IS_MULTIPLE_OF, base, { subject % base == 0 })
enum class DescriptionIntAssertions(override val value: String) : ISimpleTranslatable {
IS_MULTIPLE_OF("is multiple of")
}
Typically you would put DescriptionIntAssertions
into an own module (jar)
so that it could be replaced (with zero performance cost) by another language representation.
For instance,
atrium-cc-en_UK-robstoll
uses atrium-translations-en_UK
whereas
atrium-cc-de_CH-robstoll
uses atrium-translations-de_CH
.
But you can also use a
ITranslationSupplier
based solution and configure the ReporterBuilder
accordingly.
Robstoll's implementation
of the core of Atrium provides properties files based ITranslatableSupplier
s which are more or less what
Resource Bundle
provides out of the box.
Yet, robstoll's implementation uses an own
ResourceBundle.Control
which provides an enhanced fallback mechanism.
For further technical information, see
ResourceBundleBasedTranslator
and have a look at the
specifications of the ITranslationSupplier
s
for an example how you have to configure the ReporterBuilder
.
Notice, Atrium does not yet support generating multiple reports (in different languages) -- but Atrium is designed to support this use case. Hence, if you need this feature, then please let me know it by writing a feature request.
Let us rewrite the isEven
assertion function from the section Write own Assertion Functions
as second example:
fun IAssertionPlant<Int>.isEven() = createAndAddAssertion(
DescriptionCommon.IS, TranslatableRawString(DescriptionIntAssertions.EVEN), { subject % 2 == 0 })
enum class DescriptionIntAssertions(override val value: String) : ISimpleTranslatable {
EVEN("an even number")
}
Once again we are wrapping the text which we want to be able to exchange with another language into an ITranslatable
.
But this time we cannot use it directly but have to wrap it into an TranslatableRawString
so that it is treated as raw string in reporting.
Following on the example in the previous section,
we want to write isMultipleOf
such 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.
We split up the function in two parts: API and implementation (well yes, its that simple).
Moreover we put the API function in one module (jar) and the implementation in another.
In the implementation module we define, what we will call now an impl-function -- Atrium starts impl-functions with _
):
fun _isMultipleOf(plant: IAssertionPlant<Int>, base: Int) =
BasicAssertion(DescriptionIntAssertions.IS_MULTIPLE_OF, base, { plant.subject % base == 0 })
Notice that it is not an extension function as before
because we do not want to pollute the API of IAssertionPlant<Int>
with this function.
In the API module we define the extension function and call the impl-function:
fun IAssertionPlant<Int>.isMultipleOf(base: Int)
= addAssertion(_isMultipleOf(this, base))
We do no longer have to create the assertion as in the example of
Write own Assertion Functions.
Therefore we use the addAssertion
method and call the impl-function which will create the assertion for us.
You are ready to go, creating an API in a different language -- e.g. in German -- is now only a routine piece of work:
fun IAssertionPlant<Int>.istVielfachesVon(base: Int)
= addAssertion(_isMultipleOf(this, base))
Atrium supports currently two APIs, one in English and one in German.
Both have their design focus on interoperability with code completion functionality of your IDE
-- so that you can just type .
and let your IDE do some of the work.
Atrium is built up by different modules and it is your chose which implementation you want to use. Atrium provides two dependencies which bundle implementations so that you just have to have a dependency on that one bundle:
You are very welcome to contribute:
- open an issue or create a feature request
- fork the repository and make a pull request
- ask a question so that I better understand where Atrium needs to improve.
The code documentation is generated with dokka and is hosted on github-pages: KDoc of atrium
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 me know if you miss something by creating a feature request.
Some assertion functions which I miss myself will follow in the next version. They are listed in the Roadmap below.
Atrium does especially not support yet:
- assertion functions for floating point numbers (where precision matters)
- infinite
Iterable
s - assertion functions for
Sequence
(you can useasIterable
in the meantime)
So far there have not been frequently asked questions but you are invited to ask your question in the atrium-kotlin Slack channel.
I plan that Atrium will support in the future:
- Assertion functions for
Iterable
with nullable elements - Generating testing reports in html
- Inclusion of mockito's verify (so that it appears in the report as well)
Feature Requests and are very welcome.
Atrium is published under EUPL 1.2.