Guinness
Guinness is a port of the Jasmine library to Dart. It is based on the AngularDart implementation of Jasmine.
Importing the Library
import 'package:guinness/guinness.dart';
main() {
//you specs
}
If you are testing a client-side application, and you want to use html matchers, import the guinness_html
library.
import 'package:guinness/guinness_html.dart';
main() {
guinnessEnableHtmlMatchers();
//you specs
}
Syntax
Guinness specs are comprised of describe
, it
, beforeEach
, and afterEach
blocks.
import 'package:guinness/guinness.dart';
main(){
describe("syntax", () {
beforeEach(() {
print("outer before");
});
afterEach(() {
print("outer after");
});
it("runs first", () {
print("first");
});
describe("nested describe", () {
beforeEach(() {
print("inner before");
});
afterEach(() {
print("inner after");
});
it("runs second", () {
print("second");
});
});
});
}
This will print:
outer before, first, outer after
outer before, inner before, second, inner after, outer after
- To exclude a
describe
, change it toxdescribe
. - To exclude an
it
, change it toxit
. - To make a
describe
exclusive, change it toddescribe
. - To make an
it
exclusive, change it toiit
.
If there is an iit
in your spec files, Guinness will run only iit
s. In this case ddescribe
s will be ignored.
Async
Since Dart has built-in futures, the Guinness framework makes a good use out of them. If you return a future from
beforeEach
, afterEach
, or it
, the framework will wait for that future to be resolved.
For instance:
beforeEach(connectToTheDatabase);
where connectToTheDatabase
returns a future.
Similarly, you can write:
afterEach(releaseConnection);
You can also write async specs using the following technique:
it("should return an empty list when the database is empty", () {
return queryDatabase().then((results){
expect(results).toEqual([]);
});
});
If a returned future gets rejected, the test fails.
Expect
They way you write assertions in Guinness is by using the expect
function, as follows:
expect(2).toEqual(2);
These are a few examples:
expect(2).toEqual(2);
expect([1,2]).toContain(2);
expect(2).toBe(2);
expect(()=> throw "BOOM").toThrow();
expect(()=> throw "BOOM").toThrow("BOOM");
expect(()=> throw "Invalid Argument").toThrowWith(message: "Invalid");
expect(()=> throw new InvalidArgument()).toThrowWith(anInstanceOf: InvalidArgument);
expect(()=> throw new InvalidArgument()).toThrowWith(type: ArgumentException);
expect(false).toBeFalsy();
expect(null).toBeFalsy();
expect(true).toBeTruthy();
expect("any object").toBeTruthy();
expect("any object").toBeDefined();
expect(null).toBeNull();
expect("not null").toBeNotNull();
expect(2).not.toEqual(1);
expect([1,2]).not.toContain(3);
expect([1,2]).not.toBe([1,2]);
expect((){}).not.toThrow();
expect(null).not.toBeDefined();
expect(new DocumentFragment.html("<div>some html</div>"))
.toHaveHtml("<div>some html</div>");
expect(new DocumentFragment.html("<div>some text</div>"))
.toHaveText("some text");
expect(new DivElement()..classes.add('abc'))
.toHaveClass("abc");
expect(new DivElement()..attributes['attr'] = 'value')
.toHaveAttribute("attr");
expect(new DocumentFragment.html("<div>some html</div>"))
.not.toHaveHtml("<div>some other html</div>");
expect(new DocumentFragment.html("<div>some text</div>"))
.not.toHaveText("some other text");
expect(new DivElement()..classes.add('abc'))
.not.toHaveClass("def");
expect(new DivElement()..attributes['attr'] = 'value')
.not.toHaveAttribute("other-attr");
final select = new SelectElement();
select.children
..add(new OptionElement(value: "1"))
..add(new OptionElement(value: "2", selected: true))
..add(new OptionElement(value: "3"));
expect(select).toEqualSelect(["1", ["2"], "3"]);
You can also use unittest matchers, like this:
expect(myObject).to(beValid); //where beValid is a unittest matcher
Extending Guinness
If you are using a lot of custom matchers, and using expect(object).to(matcher)
is tedious,
you can extend the library, as follows:
library test_helper;
import 'guinness.dart' as gns;
export 'guinness.dart';
final _m = gns.guinness.matchers;
class CustomExpect extends gns.Expect {
CustomExpect(actual) : super(actual);
toBePositive() => _m.expect(actual > 0, true, reason: 'is not positive');
}
CustomExpect expect(actual) => new CustomExpect(actual);
Guinness and Karma
Guinness works with Karma. Just include initSpecs
, as follows:
files: [
"test/main1_test.dart",
"test/main2_test.dart",
"packages/guinness/init_specs.dart",
{pattern: '**/*.dart', watched: true, included: false, served: true}
]
Implementation Details
Key Ideas
The main idea is to treat the Jasmine syntax as a domain specific language. Therefore, the implementation clearly separates such things as: syntax, semantic model, and execution model. Let’s quickly look at the benefits this approach provides:
The semantic model is separate from the syntax.
The semantic model consists of It, Describe, Suite, BeforeEach, and AfterEach objects. You can create and analyse them without using the context-dependent nested Jasmine syntax.
The parsing of specs is separate from the execution of specs.
The library builds a tree of the It, Describe, Suite, BeforeEach, and AfterEach objects first. And after that, as a separate step, executes them. It enables all sorts of preprocessing (e.g., filtering, reordering).
Pluggable backends.
Since the library is a DSL, there can be multiple backend libraries actually executing the specs. By default, the library comes with the unittest backend.
Contributors
- Google Inc
- Victor Savkin
- Victor Berchet
- Marko Vuksanovic