After last night, I have helpered my way to a nice looking Dart test:
test("clicking the project menu item opens the project dialog", (){
helpers.click('button', text: '☰');
helpers.click('li', text: 'Projects');
expect(
queryAll('div').map((e)=> e.text).toList(),
contains(matches('Saved Projects'))
);
});
That's downright pretty, except… that expect()
is, let's face it, plain yucky.The thing that I am testing is kinda OK. It is a map of the text contents of
<div>
elements. I could do without the map()
, but it's not horrible. The toList()
is bothersome. It can be omitted, but it is useful to have around. For instance, if I cause an intentional failure by naming the menu "Saved Code" instead of "Saved Projects", I get a nice error message that gives me an idea of what went wrong:FAIL: project menu clicking the project menu item opens the project dialog Expected: contains match 'Saved Projects' but: was <[☰X Saved Code , ☰, X, , , , , , , , , , , , , X, , , , Saved Code ]>.If I omit the
toList()
, then the object that I am testing is an Iterable
. It still passes or fails as desired, but the failure message is less nice:FAIL: project menu clicking the project menu item opens the project dialog Expected: contains match 'Saved Projects' but: was <Instance of 'MappedListIterable':554001401>.So I leave
toList()
as a bit of test code ugliness so that I get nicer test code output. I can live with that.The expected value is not quite as nice. Dart provides a nice matcher for lists:
contains()
. So in this case I am expecting a list that contains something. That something is anything that matches the string 'Saved Projects'
. It works, but it is hard to read.But hard to read matchers are what custom matchers are for. For this expectation, I may not even need to write an entirely new matcher. Instead, I start by inheriting from
CustomMatcher
. These nifty little things set a description ("List of elements") and a feature name ("Element list content") in the constructor:class ElementListMatcher extends CustomMatcher { ElementListMatcher(matcher) : super("List of elements", "Element list content", matcher); featureValueOf(elements) => elements.map((e)=> e.text).toList(); }I then pick a value to extract from the actual value. If the actual value is a list of elements, then the above extracts the text contents in List form. That is just what I had to do by hand in my test, but all of the ugliness of mapping and converting from an iterable to a list is done in the custom matcher.
I then create a top-level helper that uses this matcher to check the extracted/featured list to see if it contains a match:
elements_contain(Pattern content) => new ElementListMatcher(contains(matches(content)));Those are two pretty simple helpers that let me rewrite my test entirely with helpers as:
test("clicking the project menu item opens the project dialog", (){
helpers.click('button', text: '☰');
helpers.click('li', text: 'Projects');
expect(
queryAll('div'),
helpers.elements_contain('Saved Projects')
);
});
That is pretty all the way through: I click a button with the menu icon, I click the "Projects" menu item, then I expect one of the <div>
tags to contain the text "Saved Projects". Wonderful!And best of all it works!
If I again intentionally make my test fail, I get:
FAIL: project menu clicking the project menu item opens the project dialog Expected: List of elements contains match 'Saved Projects' but: Element list content was <[☰X Saved Code , ☰, X, , , , , , , , , , , , , X, , , , Saved Code ]>.That is even more expressive than what I had when I manually extracted a list to test and the test content is easier to read. The use of the
helpers
prefix from last night is the only remaining noise (and I still think it worth keeping about). All in all, these test matchers are pretty powerful.Day #758
No comments:
Post a Comment