We've made failure a strategy
Continuous Integration:
Latest Release: 0.13
Latest Artifact: org.junit.contrib:truth:jar:0.13
Note: Truth is subject to change and prior to 1.0, may introduce breaking changes. We're getting closer to "prime time" but please use with caution before release 1.0. Consider Truth in alpha.
###Table of Contents
Truth is a testing framework suitable for making assertions and assumptions about code. Truth adopts a fluent style for your test propositions, is extensible in several ways, supports IDE completion/discovery of available propositions, and supports different responses to un-true propositions. Truth can be used to declare assumptions (skip the test if they fail), assertions (fail the test), and expectations (continue but report errors and fail at the end).
While intended to work with JUnit, Truth can be used with other testing framework with minimal effort. Truth is released as a maven artifact through a custom repository (it will be released to repo1.maven.org soon), and is licensed with the Apache 2.0 open-source license. As such, you are free to use it or modify it subject only to the terms in that license.
To prepare to use Truth, declare this dependency in maven or an equivalent:
<dependency>
<groupId>org.truth0</groupId>
<artifactId>truth</artifactId>
<version>0.13</version>
</dependency>
or download the jar directly from the link below and add it to your tests classpath
http://search.maven.org/remotecontent?filepath=org/truth0/truth/0.13/truth-0.13.jar
A brief and basic tutorial
###Super basics
Truth is used in a literate style. One can use truth most simply to replace JUnit assertions (and to handle the dreaded "MoreAsserts") problem where a team must increasingly create more static assert libraries to handle complex cases.
Most simply, replacing a JUnit assert like this:
assertTrue(blah.isSomeBooleanValue());
can be done with the following:
Truth.ASSERT.that(blah.isSomeBooleanValue()).isTrue();
... or with static imports, this can be shortened to the preferred usage:
ASSERT.that(blah.isSomeBooleanValue()).isTrue();
This may not seem like a saving, but for two things. One, it reads, in english, precisely what it means. And secondly, many assertTrue() calls are hiding more meaningful assertions, like this:
assertTrue(blah > 5);
In Truth, you would tend to extract all of these:
ASSERT.that(blah).isMoreThan(5);
And where one might write:
assertTrue(blah.isEmpty());
Truth would have you write:
ASSERT.that(blah).isEmpty();
Truth treats failure as data - a proposition was not true... so now what? Well, that depends on the failure strategy. Truth supports a few different strategies for handling failure, and has different strategies baked in to pre-built TestVerbs exposed as static final fields. The standard strategies are:
Strategy | Constant | Behaviour | Framework supported | Notes |
---|---|---|---|---|
Assertion | Truth.ASSERT | Aborts and fails test, reports failure | JUnit, TestNG, others (untested) | |
Assumption | Truth.ASSUME | Aborts and ignores/skips test | JUnit | |
Expectation | Expect.create() | Continues test, reports errors and failure upon test completion | JUnit | You must declare an @Rule per the ExpectTest |
Note: These different styles can let a developer build more supple tests, though the Truth team recommends mostly using ASSERT in unit tests, and very careful consideration of ASSUME and EXPECT. These can make one's tests quite expressive and clear, but ASSUME can cause tests to not be run (unexpectedly), and EXPECT can encourage the developer to test propositions about way too many things, causing big heavy tests, rather than lots of small, clear tests.
Truth presents you with a test verb (ASSERT and ASSUME are built in, and EXPECT is supported with JUnit4 @Rules). The verb is asserting on a subject, the value to be considered.
ASSERT.that(thisSubject)
Once Truth has a subject, it has a known type, and can therefore reason at compile time about what propositions are known about that subject. For instance, integers, longs, booleans, strings, and various flavours of collections all have different kinds of things you want to know about them. Because Truth knows about these types at compile time, it returns a "Subject" wrapper around your value, which declares proposition methods such as "contains" or "isMoreThan" or "isEmpty" etc. These allow your IDE to suggest available completions.
But what if a proposition isn't supported for the desired type? Or the type is not a part of Truth's basic types at all? How can a developer use Truth on "TheirCustomObject"?
Truth uses two means to do this. The simplest is not recommended, but is quick and dirty and can get the job done. That's simply to extend AbstractVerb or TestVerb and declare their own custom ASSERT field containing this custom verb. That verb can implement more "that(Sometype t)" overrides to support custom types, or even to provide a different Subject wrapper for the already supported types. (see Extensibility through Subclassing)
A more literate approach, and one which doesn't require creating a new TestVerb (allowing reuse of ASSERT, ASSUME, and EXPECT) is to use this syntax:
ASSERT.about(SOME_TYPE).that(thatValue).hasSomePropositionalValue();
SOME_TYPE here is actually a SubjectFactory - an interface which can be implemented to provide a custom Subject wrapper. Creating a SubjectFactory for use in this approach is pretty easy, and you can follow the example given it the Extension through delegation example.
For convenience, you can create a static final SOME_TYPE field so you can use it less-verbosely in ASSERT.about(); Existing Subject subclasses (e.g. IntegerSubject, StringSubject, etc.) all have static final SubjectFactory fields named INTEGER, STRING, etc. You can also follow their example.
Sometimes a test needs to look at the contents of a collection and ensure that characteristics of all a collection's contents conform to certain constraints. This can be done with a for-each-like approach.
ASSERT.in(anIterable).thatEach(STRING).has().item("foo");
This lets you pass in an iterable type, and provide a SubjectFactory
(it is advised to use the static final fields for this for readability).
When this is invoked, then contains("foo")
will be invoked
on each element in the iterable in turn, reporting failure as if a
separate ASSERT.that(anElement).contains("foo") had been invoked for
each element. Naturally, ASSERT will fail on the first failing element,
ASSUME will skip the test on any failing element, and EXPECT will
gather all failures and report them at the end of the test.
This approach can be used for custom types too, so long as they have a SubjectFactory
public static final SubjectFactory<MyCustomSubject, MyType> MY_TYPE = ...;
...
ASSERT.in(someIterable).thatEach(MY_TYPE).doesSomething();
The same extensibility provided in ASSERT.about(MY_TYPE).that()...
is available to the developer over iterables of that type.
These are not exhaustive examples, but commonly used propositions. Please check out the Javadocs for the Subject subclasses, or use IDE syntax completion proposals to discover predicates for types you provide as subjects.
Equality is simply "is" in Truth.
ASSERT.that(this).is(that);
Type information is as usual:
ASSERT.that(this).isA(MyObject.class);
Often propositions have negative forms:
ASSERT.that(this).isNotA(String.class);
Nullness is checked simply with:
ASSERT.that(something).isNull();
ASSERT.that(somethingElse).isNotNull();
Fields' presence and their values can (as of 0.8) be checked with:
ASSERT.that(something).hasField("foo").withValue("bar");
This should work even with private fields, and can be useful in testing generated properties created by some frameworks like Lombok and Tapestry.
ASSERT.that(aClass).declaresField("foo");
- Note, do not use hasField() on Class objects, as you will be testing whether Class.class itself has that field, not whether the type it represents declares that field. A deprecation warning should notify you of this usage, but be careful, and use declaresField("foo") instead.
ASSERT.that(something).isTrue();
ASSERT.that(something).isFalse();
ASSERT.that(5).isBetween(4, 5);
ASSERT.that(5).isExclusivelyInRange(4, 6);
ASSERT.that(aString).contains("blah");
ASSERT.that(aString).startsWith("foo");
ASSERT.that(aString).endsWith("bar");
ASSERT.that(anIterable).isEmpty();
ASSERT.that(anIterable).iteratesOverSequence(a, b, c);
One can simply use object equality if you want to test collection equivalence, given the guarantees of Collections' implementations of .equals():
ASSERT.that(colectionA).is(collectionB);
Testing properties like size should be done like so:
ASSERT.that(collection.size()).is(5);
Or you can test that a specific item is present present:
ASSERT.that(collectionA).has().item(q);
Or you can test that all provided items are present:
ASSERT.that(collectionA).has().allOf(a, b, c);
Or you can be even more explicit and test that all and only the provided items are present:
ASSERT.that(collectionA).has().exactly(a, b, c, d);
optionally you can further constrain this:
ASSERT.that(collectionA).has().allOf(a, b, c).inOrder();
ASSERT.that(collectionA).has().exactly(a, b, c, d).inOrder();
Or you can assert using a (very) limited "or" logic with:
ASSERT.that(collectionA).has().anyOf(b, c);
You can also pass in collections as containers of expected results, like so:
ASSERT.that(collectionA).has().allFrom(collectionB);
ASSERT.that(collectionA).has().anyFrom(collectionB);
ASSERT.that(collectionA).has().exactlyAs(collectionB).inOrder();
Specific properties can be proposed on lists, such as:
ASSERT.that(myList).isOrdered(); // uses default ordering and is strict, no equal elements.
ASSERT.that(myList).isPartiallyOrdered(); // like isOrdered, but equal elements may be present.
And custom comparators can be provided
ASSERT.that(myList).isOrdered(aComparator);
ASSERT.that(myList).isPartiallyOrdered(aComparator);
Presence of keys, keys for values, or values can be asserted
ASSERT.that(map).hasKey("foo");
ASSERT.that(map).hasKey("foo").withValue("bar");
ASSERT.that(map).lacksKey("foo");
ASSERT.that(map).hasValue("bar");
Naturally, also:
ASSERT.that(map).isEmpty();
ASSERT.that(map).isNotEmpty();
Testing properties like size should be done like so:
ASSERT.that(map.size()).is(5);
A subset of Truth functionality is GWT compatible. Most propositions and subjects that do not inherently use reflection or complex classloading are available. This mostly excludes categorical testing of collection contents since that uses code generation not supportable (as is) on GWT, as well as ClassSubjects, which largely concerns itself with the internals of Java classes and testing them. Also, raw field access is not supported, though this might be in the future if there is enough of a use-case for it.
- Subject wrappers for new types:
- New subjects for Float/Double and other currently missing types.
- Support for Annotations and methods and field availability on classes.
- Support for Protocol Buffers, JavaBeans (and other reflective types)
- Support for Guava collections and types (Multimaps, Multisets, etc.)
- New subjects for Float/Double and other currently missing types.
- New propositions on existing Subject wrappers:
- StringSubject, IntegerSubject, etc.
Thanks to Github and Cloudbees for having a strong commitment to open-source, and providing us with tools so we can provide others with code. And thanks to Google for Guava, and for encouraging us to try to solve problems in better ways and share that with the world.
Also thanks to the authors of JUnit, TestNG, Hamcrest, FEST, and others for creating testing tools that let us write high-quality code, for inspiring this work and for moving the ball forward in the field of automated software testing. This project works with, works alongside, and sometimes works in competition with the above tools, but owes a debt that everyone owes to those gone before. They paved the way, and we hope this contribution is helpful to the field.