I must have detached the bulk of my Dart for Hipsters tests from the main test suite back when I first started on the latest rewrite. Since that was such a long time ago, I have since forgotten, leaving me with a bunch of tests that are not running. In other words, I have a bunch of worthless tests.
As I work through each chapter's worth of tests, hooking the old tests back up, I am pleasantly surprised when they pass. At the same time, I am eager for failures because that means that something may have changed—something may be new to me. Tonight, as I was working through the chapter on dynamic language features, I ran into some problems with my
noSuchMethod()
code. Not wanting a repeat of last night's debacle in which I very nearly repeated a post, tonight I search for and find the original post on this code. That does not include the somewhat recent switch from invocation “mirrors” to a plain-old invocation objects. That introduction article does a pretty good job of explaining the errors that I am seeing in my tests:
ERROR: [noSuchMethod] can pass parameters to noSuchMethod in superclass Test failed: Caught NoSuchMethodError : method not found: 'bar' Receiver: top-level Arguments: [2, 2] ../varying_the_behavior/test/superclass_no_such_method.dart 13:5 A.noSuchMethod ../varying_the_behavior/test/superclass_no_such_method.dart 28:30 C.noSuchMethod ../varying_the_behavior/test/superclass_no_such_method.dart 46:19 run.My code is actually reaching the. package:unittest/src/test_case.dart 111:30
throw
statement in my noSuchMethod()
even though my test is trying to invoke the bar()
method:class A { noSuchMethod(args) { if (args.isMethod && args.memberName == "bar") { return 2 * args. positionalArguments. reduce(0, (prev, element) => prev + element); } throw new NoSuchMethodError( this, args.memberName, args.positionalArguments, args.namedArguments ); } }The problem is that
memberName
is no longer a string (“bar”). It is now a Symbol
version of that string. It is interesting what a language sometimes has to do to itself when it has to compile into a another language. In this case, my Dart test code needs to work when compiled down to JavaScript: test('can access noSuchMethod in superclass when defined in subclass', (){
var c = new C();
expect(()=> c.bar(2, 2), returnsNormally);
});
The problem, as the announcement article explains so well, is not so much that the above code can be compiled to JavaScript. Rather the problem is that the above test code might get compiled into minified JavaScript. In other words, the bar()
method might get minified as a()
in which case a string-based memberName
would return “a” when my noSuchMethod()
definition supports only “bar.”Enter
Symbol
, which is treated differently in by the dart2js
compiler. To put that to use in my noSuchMethod()
definition, I compare memberName
, which is a Symbol
, to a Symbol
object that I create:class A { noSuchMethod(args) { if (args.isMethod && args.memberName == const Symbol("bar")) { return 2 * args. positionalArguments. fold(0, (prev, element) => prev + element); } // ... } }That is all that I need to do in order to make my test pass.
I am not quite done, though. Happily the invocation announcement article also includes a way to clean up my messy, six line throw-no-such-method
throw
statement. The bar()
method is the only method that I support via noSuchMethod()
in this class, so when something else is called, I need to raise an error which I had been doing with:class A { noSuchMethod(args) { if (args.isMethod && args.memberName == "bar") { /* ... */ } throw new NoSuchMethodError( this, args.memberName, args.positionalArguments, args.namedArguments ); } }Mercifully, there is a better way—by invoking
noSuchMethod()
on super
:class A { noSuchMethod(args) { if (args.isMethod && args.memberName == const Symbol("bar")) { /* ... */ } return super.noSuchMethod(args); } }In other words, let
Object
, or whatever other superclass might be involved, deal with the args
Invocation
object. Yay! Much nicer.I cannot say that I am 100% sold on the idea of
Symbol
. It helps greatly to understand that the need for Symbol
comes from the need to support JavaScript, but that's the problem. It made little sense to me when skimming the documentation what purpose the Symbol
class served. And really, its only purpose seems to be to make JavaScript compiling work. Perhaps someday Dart will throw in a little Ruby-like syntactic sugar for defining symbols to make the benefit a little more obvious.Until then… the tests for another chapter are passing!
Day #877
No comments:
Post a Comment