Sunday, October 6, 2013

Dart's Timer is the Devil (in tests)


I have grown to think of Dart's Timer as a personal failure. The kind of failure that results in bad things happening to cute, innocent animals. Oh, there are some legitimate uses of Timer to be sure, but I seem to use it as crutch whenever I cannot understand some asynchronous behavior or other.

My Timer abuse is at its worst in tests. Consider last night's test in which I had to use not one but two instances of Timer:
      test("can embed code much later", (){
        new Timer(
          new Duration(milliseconds: 1000),
          expectAsync0((){
            var later = createElement('ice-code-editor');
            var iceElement = later.xtag;
            iceElement.src = 'embed_baz.html';

            document.body.append(later);

            new Timer(
              new Duration(milliseconds: 1000),
              expectAsync0((){
                expect(
                  queryAll('ice-code-editor').last.shadowRoot.query('h1').text,
                  contains('embed_baz.html')
                );
              })
            );
          })
        );
      });
That's just silly. Shame on me.

The first Timer is an attempt to simulate the “later” mentioned in the test description. I am trying to verify that the Polymer.dart custom element works—js-interop and all—after all JavaScript has loaded. I found that the second Timer was necessary as I was exploring adding dynamic attributes to my Polymer.dart custom element for the ICE Code Editor. Hopefully I can find better alternatives to both tonight.

The first is meant to ensure that the JavaScript has fully loaded and been evaluated. As part of last night's work, I now have an Editor.jsReady Future that will complete when the JavaScript is fully loaded. I can use that as part of my test's setUp():
      group("after JS has loaded and been evaluated", (){
        setUp(()=> Editor.jsReady);

        test("can still embed code", (){
          // Same test, but with one Timer now...
        });
      });
Returning a Future, like Editor.jsReady, from a Dart unittest setUp() will block the test until the Future has completed. So I have removed one Timer and gotten a more accurate, deterministic test as a result.

As for that second Timer, I would swear before powerful and mysterious forces that I could only get the test to pass if I made it wait for nearly a full second. This was late last night, so I would not swear before the gods of my chain that the full second was required. My certainly lies somewhere between fear of vague and unknown mystical forces and fear of the very strict chain gods (who are kind and beneficent when you don't mess with them). Anyhow...

Tonight, I find that I can reduce the second Timer all the way to a duration of zero. In other words, I can use plain-old Timer.run():
      group("after JS has loaded and been evaluated", (){
        setUp(()=> Editor.jsReady);

        test("can still embed code", (){
          var later = createElement('ice-code-editor')
            ..xtag.src = 'embed_baz.html';

          document.body.append(later);

          Timer.run(
            expectAsync0((){
              expect(
                queryAll('ice-code-editor').last.shadowRoot.query('h1').text,
                contains('embed_baz.html')
              );
            })
          );
        });
      });
A Timer instance with an arbitrary duration before running code is a lightning rod for my ire. Timer.run(), on the other hand, I can abide. An arbitrary Timer duration says to me that there are mysterious things in my code that I do not understand. Test.run() says that I need to give my code one event loop to render a change. Ultimately, this is what the code is doing—updating the DOM after the described change. So I can live with a test that codifies that.

That said, this might be a good use case for scheduled_test. Perhaps something to explore tomorrow (along with why the preview is no longer being updated). For now, I am satisfied with killing off two Timer instances. I may believe in and fear mysterious gods, but that is no reason to suffer mystery in my tests.


Day #896

No comments:

Post a Comment