Writing unit tests is easy with Dlang. The unittest
block allows you to start writing tests and to be productive with no special setup.
Unfortunately the assert expression does not help you to write expressive asserts, and in case of a failure it's hard to find why an assert failed. The fluent-asserts
library allows you to more naturally specify the expected outcome of a TDD or BDD-style test.
- Add the DUB dependency: https://code.dlang.org/packages/fluent-asserts
$ dub add fluent-asserts
- Use it:
unittest {
true.should.equal(false).because("this is a failing assert");
}
unittest {
Assert.equal(true, false, "this is a failing assert");
}
- Run the tests:
➜ dub test --compiler=ldc2
The library provides the expect
, should
templates and the Assert
struct.
expect
is the main assert function exposed by this library. It takes a parameter which is the value that is tested. You can
use any assert operation provided by the base library or any other operations that was registered by a third party library.
Expect expect(T)(lazy T testedValue, ...);
Expect expect(void delegate() callable, ...);
...
expect(testedValue).to.equal(42);
In addition, the library provides the not
and because
modifiers that allow to improve your asserts.
not
negates the assert condition:
expect(testedValue).to.not.equal(42);
because
allows you to add a custom message:
expect(true).to.equal(false).because("of test reasons");
/// will output this message: Because of test reasons, true should equal `false`.
should
is designed to be used in combination with Uniform Function Call Syntax (UFCS), and
is an alias for expect
.
auto should(T)(lazy T testData, ...);
So the following statements are equivalent
testedValue.should.equal(42);
expect(testedValue).to.equal(42);
In addition, you can use not
and because
modifiers with should
.
not
negates the assert condition:
testedValue.should.not.equal(42);
true.should.equal(false).because("of test reasons");
Assert
is a wrapper for the expect function, that allows you to use the asserts with a different syntax.
For example, the following lines are equivalent:
expect(testedValue).to.equal(42);
Assert.equal(testedValue, 42);
All the asserts that are available using the expect
syntax are available with Assert
. If you want to negate the check,
just add not
before the assert name:
Assert.notEqual(testedValue, 42);
- above
- approximately
- beNull
- below
- between
- contain
- containOnly
- endWith
- equal
- greaterOrEqualTo
- greaterThan
- instanceOf
- lessOrEqualTo
- lessThan
- startWith
- throwAnyException
- throwException
- throwSomething
- within
Even though this library has an extensive set of operations, sometimes a new operation might be needed to test your code. Operations are functions that recieve an Evaluation
and returns an IResult
list in case there was a failure. You can check any of the built in operations for a refference implementation.
IResult[] customOperation(ref Evaluation evaluation) @safe nothrow {
...
}
Once the operation is ready to use, it has to be registered with the global registry:
static this() {
/// bind the type to different matchers
Registry.instance.register!(SysTime, SysTime)("between", &customOperation);
Registry.instance.register!(SysTime, SysTime)("within", &customOperation);
/// or use * to match any type
Registry.instance.register("*", "*", "customOperation", &customOperation);
}
In order to setup an Evaluation
, the actual and expected values need to be converted to a string. Most of the time, the default serializer will do a great job, but sometimes you might want to add a custom serializer for your types.
static this() {
SerializerRegistry.instance.register(&jsonToString);
}
string jsonToString(Json value) {
/// you can add here your custom serializer for Jsons
}
MIT. See LICENSE for details.