Thanks to the magic of
dart_analyzer
I know that my Dart Comics example Dart application is in exceptional shape. +Kasper Lund pointed out that my exception handling might be a bit off—despite the fact that dart_analyzer
did not complain about it.The problem is that in my haste to ensure that the
remove()
method in my event listener list class was not supported, I am not actually throwing an error:abstract class HipsterEventListenerList implements EventListenerList { // ... EventListenerList remove(EventListener listener, [bool useCapture=false]) { throw UnsupportedError; } // ... }Instead of throwing an error, I am throwing an error class. That is allowed per the dart language spec. Any old object can get thrown—it does not have to be an exception or error subclass. Indeed it seems that I can even throw classes. But I do not believe that it is possible to catch that error.
To test that theory, I write a test. This particular class resides in the Hipster MVC package, so I will add my test there.
I am going to need a browser for this test since the event classes are based on the events in the
dart:html
library, which requires a browser (and there are no headless Dart environments yet). The skeleton of the test imports the unittest library, the enhanced HTML reporter (for pretty browser reporting), and the events library:import 'package:unittest/unittest.dart'; import 'package:unittest/html_enhanced_config.dart'; import 'package:hipster_mvc/hipster_events.dart'; class TestEventListenerList extends HipsterEventListenerList {} main() { useHtmlEnhancedConfiguration(); // tests go here... }I need a concrete class to test the
remove()
method in the abstract HipsterEventListenerList
, hence TestEventListenerList
above. In the main()
entry point, I start the HTML reporter.As for the test itself, I define a group in which I will describe all unsupported methods (currently only
remove
). Groups are not strictly required by the testing library, but they help with the output. In the test proper, I create an instance of TestEventListenerList
so that I can test it. As for my expectation, I expect the result of invoking an anonymous function that removes a dummy value from the list will throw an unsupported error:main() { useHtmlEnhancedConfiguration(); group('unsupported', () { test('remove', (){ var it = new TestEventListenerList(); expect( () => it.remove("foo"), throwsUnsupportedError ); }); }); }If I do not use an anonymous function inside the
expect
function, then it.remove()
would get evaluated before the expect
call. That is, the following would not work: test('remove', (){
var it = new TestEventListenerList();
expect(
it.remove("foo"),
throwsUnsupportedError
);
The it.remove("foo")
would throw an UnsupportedError
before expect even has a chance to evaluate its two parameters.As an aside, I think that I appreciate the distinction between errors (non-recoverable) and exceptions (exceptional occurrences, but recoverable). In this case, code that expects that an event listener was removed would almost certainly be in an unstable state, which should crash the application. Hopefully an intrepid contributor would then feel compelled to submit a patch.
Anyhow, back to my original problem. What happens when I run this test against my code that throws a class instead of an object? The test crashes, dumping the following into the console:
Internal error: 'file:///home/chris/repos/hipster-mvc/test/packages/hipster_mvc/hipster_events.dart': Error: line 32 pos 11: illegal use of class name 'UnsupportedError' throw UnsupportedError; ^Ah! So
dart_analyzer
might give me a pass on this, but the Dart VM in Dartium does not.The next step is easy enough: I have my failing (well, not compiling) test. Let's make it pass. The fix is similarly easy—I just need to throw an instance of
UnsupportedError
:abstract class HipsterEventListenerList implements EventListenerList { // ... EventListenerList remove(EventListener listener, [bool useCapture=false]) { throw new UnsupportedError("Planned in a future release"); } // ... }With that, I have my first Hipster MVC test passing:
I appreciate being able to drive this bug fix with tests, which are easy in Dart since the unittest library is available as a Dart Pub package. Hopefully this particular test will not be long-lived, and will be replaced by a test that verifies removal of an event listener. Still, it is nice to have the ability to drive functionality with a reusable test (of course, I would still prefer a way to do so headless).
I also really like the test matchers that are built into the unit testing library. It is nice being able to write
throwsUnsupportedError
rather than setting my expectation on the outcome of a try-catch block. Reporting—especially when tests fail—is much nicer with matchers like this.Day #605
No comments:
Post a Comment