Wednesday, July 10, 2013

Can't Stub Dart HttpRequest with Sinon.js


I'm pretty sure this won't work, but…

I have been trying to get Sinon.js-like stubbing working in Dart. I wonder if I can simply use Sinon.js via js-interop? I have the feeling that this will not work in Dartium (or content_shell from the command line) because Dart is directly using the native implementation of HttpRequest built into the browser.

Still, it seems worth a shot.

So I grab the latest sinon.js and install it in Hipster MVC's test directory:
➜  test git:(http-test-server) wget http://sinonjs.org/releases/sinon-1.7.3.js                    
2013-07-10 21:16:52 (109 KB/s) - `sinon-1.7.3.js' saved [132684/132684]
I will need js-interop, which now involves two changes: installation via Dart Pub and updating the test's web page context to support it.

Installing js-interop via pub is a simple matter of updating my pubspec.yaml to include js as a dependency, which… Hipster MVC already includes:
name: hipster_mvc
version: 0.2.6
description: Dart-based MVC framework
author: Chris Strom <chris@eeecomputes.com>
homepage: https://github.com/eee-c/hipster-mvc
dependencies:
  unittest: any
  dirty: any
  uuid: any
  js: any
That should probably be browser: any instead of js: any. Up until this point, I have not included js-interop in Hipster MVC. My guess is that I included the js package from back before it was js-interop (I am not sure if that was ever actually the case) or if I mistakenly included it in an effort to get the browser library, which aids in Dart support in browsers that do not support Dart natively.

Anyhow, I update my pubspec.yaml to explicitly depend on browser:
name: hipster_mvc
version: 0.2.6
description: Dart-based MVC framework
author: Chris Strom 
homepage: https://github.com/eee-c/hipster-mvc
dependencies:
  browser: any
  unittest: any
  dirty: any
  uuid: any
  js: any
And then pub install:
➜  hipster-mvc git:(http-test-server) ✗ pub update
Resolving dependencies...........
Dependencies updated!
That should change nothing since the js library depends on browser. I just prefer to be explicit about my dependencies.

The other change that I need for js-interop is to include the iterop.js file which is, oddly enough, included in the browser package. I add the <script> tag for browser/interop to my test/index.html context page, along with sinon.js:
<html>
<head>
  <title>Hipster Test Suite</title>
  <script type="application/dart" src="hipster_mvc_test.dart"></script>

  <script type='text/javascript'>
    // Code to notify the test runner when all tests are done....
  </script>
  <script src="packages/browser/dart.js"></script>
  <script src="packages/browser/interop.js"></script>
  <script src="sinon-1.7.3.js"></script>
</head>

<body>
</body>
</html>
I next need to import the js-interop library into my test script:
library hipster_mvc_test;

import 'package:unittest/unittest.dart';
import 'dart:html';
import 'dart:async';
import 'dart:json' as JSON;
import 'package:js/js.dart' as js;
//...
That will allow me to reference JavaScript files under the js. prefix in Dart. I do so in the setUp() block of my test:
    group("HTTP get", (){
      var server;
      setUp(() {
        server = js.context.sinon.fakeServer.create();
        server.respondWith(
          '[{ "id": 12, "comment": "Hey there" }]'
        );
      });
      // test here...
    });
I am explicitly using the string response version of Sinon's fake server to avoid the need to convert Dart HashMaps and Lists to JavaScript objects and arrays. If I get this working with the simplest approach, then I will worry about that. In JavaScript, the above would replace the XMLHttpRequest object with a fake version that will respond to all requests with the specified JSON string.

To see if Dart is using that same XMLHttpRequest object, I write my test as:
    group("HTTP get", (){
      var server;
      setUp(() { /* ... */ });
      test("it can parse responses", (){
        var model = new FakeModel()
          ..url = '/test';
        HipsterSync.
          call('get', model).
          then(
            expectAsync1((response) {
              expect(response, containsPair('comment', 'Hey there'));
            })
          );
        server.respond();
      });
    });
This code works when hitting against a real server. The only change here is the addition of Sinon's server.respond(), which will respond to all XHR requests that have been made in my test.

Unfortunately, this does not work.

I never do see that response come back. I play around a bit with Sinon options. I set autoRespond to true. I set the URL to an http:// version instead. I even add console.log() statements directly to my copy of sinon.js. But I get no joy.

I can verify that the JavaScript XMLHttpRequest object is replaced:



But, Dart is, it would seem, by-passing this object to get to the native XHR object. That's good for Dart, but it stinks for my purposes tonight.

Just for giggles, I try compiling my test to JavaScript and running it from a normal Chrome browser. I get the following stacktrace:
FAIL: Hipster Sync HTTP get it can parse responses
  Caught NoSuchMethodError : method not found: 'Object #<JSUnknown> has no method '$$dom_addEventListener$3''
  Receiver: ""
  Arguments: []
  TypeError: Object #<JSUnknown> has no method '$$dom_addEventListener$3'
  $.$$dom_addEventListener$3$x              file:///home/chris/repos/hipster-mvc/test/hipster_mvc_test.dart.js 18979:39
  hipster_sync_tests___closure6.call$0      file:///home/chris/repos/hipster-mvc/test/hipster_mvc_test.dart.js 13852:9
  TestCase.testFunction$0                   file:///home/chris/repos/hipster-mvc/test/hipster_mvc_test.dart.js 16052:30
I try passing a Dart callback to Sinon's respondWith() on the off chance that this is what it is “JSUnknown”:
      setUp(() {
        server = js.context.sinon.fakeServer.create();
        var cb = new js.Callback.once((req) {
          req.respond(200, [], '{ "id": 12, "comment": "Hey there" }');
        });
        server.respondWith(cb);
      });
But that has no effect upon recompilation.

Ah well, it was a long shot to begin with. With definitive proof that overriding JavaScript's XMLHttpRequest will have no effect on my Dart tests, I can move onto other avenues of exploration. Tomorrow.


Day #808

No comments:

Post a Comment