Zeal is a Java assertion library built on a rich expression language. Simple, extensible, no fuss.
The Maven dependency is:
<dependency>
<groupId>io.github.libzeal</groupId>
<artifactId>zeal-assertion</artifactId>
<version>0.2.0</version>
</dependency>
The Gradle dependency is:
implementation 'io.github.libzeal:zeal-assertion:0.2.0'
A simple Zeal precondition can be written on a single line:
require(value("foo").isNotNull().isNotBlank());
The value
method creates an evaluation from the string foo
, and the require
method evaluates that expression. The isNotNull()
and isNotBlank()
calls add two predicates to the expression that are checked when it is evaluated.
While many assertion libraries are available, Zeal is designed on the principle that expressions are separated from their evaluation. Thus, the same expression can be evaluated differently -- such as a precondition or a postcondition -- and custom expressions can be supplied to any evaluator. For example, the same expression above can be evaluated as a postcondition using the ensure
method:
ensure(value("foo").isNotNull().isNotBlank());
Likewise, we can evaluate any expression as a precondition:
UnaryExpression<String> expression = // ...
require(expression);
- Simple: Expressions and evaluators clearly document developer's intent.
- Portable: Written in Java 8, OSGi compliant, and under 100 KB
- Independent: No external dependencies
- Extensible: Provides expressions for common types, but makes creating new expressions easy
Zeal is based on a few main concepts:
- Evaluators
- Expressions
An evaluator is a method that evaluates an expression, such as an assertion. Zeal has three basic types of assertions:
confirm(expression) // Confirmation (simple assertion)
require(expression) // Precondition
ensure(expression) // Postcondition
All of these are static methods of the following class:
io.github.libzeal.zeal.assertion.Assertions
A confirmation is the equivalent of a basic assert
and is created using the confirm
method, which:
- Throws an
AssertionFailedException
if the expression evaluates to false - Returns the subject of a
UnaryExpression
if the expression evaluates to true
AssertionFailedException
is a subclass of IllegalStateException
. An example of a simple assertion is:
public String processName(String name) {
String firstName = extractFirstName(name);
confirm(value(name).startsWith(firstName));
return "Hello, " + firstName;
}
A custom message can be added to the AssertionFailedException
thrown when the assertion fails:
confirm(value(name).startsWith(firstName), "First name must be first");
A precondition is created using the require
method, which:
- Throws a
NullPointerException
(NPE) if the expression fails and the subject isnull
- Throws a
PreconditionFailedException
if the expression fails for any other reason - Returns the subject of a
UnaryExpression
if the expression evaluates to true
An example of a precondition is:
class Credentials {
private final String username;
private final String password;
public Credentials(String username, String password) {
this.username = require(value(username).isNotNull().isNotBlank());
this.password = require(value(password).isNotNull().isLongerThan(8),
"Password must has sufficient length");
}
// ...other methods...
}
The convention is to throw an NPE if an argument is
null
when it is expected to be not null
. Therefore, require
will throw an NPE if the subject of the expression is null
. A PreconditionFailedException
(which is a subclass of
IllegalArgumentException
) will be thrown when an expression is evaluated to false in all other cases.
Note: The JDK throws an NPE for
Objects.requireNonNull()
and this has become the convention. There is controversy about whether an NPE orIllegalArgumentException
should be thrown. The convention of an NPE is used by default and an option is provided for throwing another exception onnull
(seeRequirement. thatThrowsOnNull
below).
A postcondition is created using the ensure
method, which:
- Throws a
PostconditionFailedException
if the expression fails for any reason - Returns the subject of a
UnaryExpression
if the expression evaluates to true
An example of a precondition is:
class Credentials {
private final String username;
private final String password;
public Credentials(String username, String password) {
this.username = require(value(username).isNotNull().isNotBlank());
this.password = require(value(password).isNotNull().isLongerThan(8),
"Password must has sufficient length");
}
// ...other methods...
}
Expressions are the heart of Zeal. An Expression
is any object that can be evaluated to true or false (PASSED
or
FAILED
, respectively in Zeal). Unary expressions in particular are critical to creating Zeal assertions.
Unary expressions evaluate a single subject. This focus on a single subject allows Zeal assertions to return the subject of an evaluation. For example:
String verifiedName = require(value(name).isLongerThan(8));
The value
method wraps a single object into a unary expression. The unary expression that is created has a set of predicates (such as isLongerThan
) that can be chained together and used when evaluating the expression. For example:
require(value("foo").isNotBlank().isLongerThan(8));
Two predicates are added to the expression:
isNotBlank()
isLongerThan(8)
These predicates are evaluated in order and all predicates must evaluate to true for the expression to evaluate to true (predicates are conjunctive). If any predicate in the chain fails, then all subsequent predicate evaluations are skipped.
There is one exception to the rule that predicates are evaluated in order: isNotNull()
. This predicate is a unique predicate because all other predicates depend on this one being true.
For example, if we try to evaluate if a Double
is greater than some value, it will fail if the subject is null
(greater than does not make sense for the absence of a value). The evaluation did not fail because the subject was not greater than the supplied value, but because the subject was null
.
Therefore, Zeal has a special rule:
If an expression has a
isNotNull()
predicate, this predicate is evaluated before any other predicate.
This ensures that if an assertion wishes to check if a subject is null
, then the evaluation fails due to the nullity of the subject, not a extraneous reason (such as isGreaterThan
). If the expression does not contain an isNotNull
predicate, the first predicate that fails will be considered the source of the failure (since the subject was not explicitly checked for nullity).
The following types have equivalent expressions supported in the core types module:
Object
String
Enum
Long
Integer
Short
Double
Float
Number
Boolean
Byte
Character