js-data / js-data-http

Http adapter for js-data. Main Site: http://js-data.io, API Reference Docs: http://api.js-data.io
MIT License
38 stars 26 forks source link

Error when attempting to use js-data-http with sinon #15

Closed techniq closed 9 years ago

techniq commented 9 years ago

I'm attempting to use js-data with JSPM and was starting by writing a test to understand how js-data / js-data-http works but have already ran into an issue. Below is the test I'm attempting to run with Karma / Chrome

import JSData from 'js-data';
import DSHttpAdapter from 'js-data-http';
import sinon from 'sinon';

describe("JSData Suite", function() {
  beforeEach(function() {
    this.xhr = sinon.useFakeXMLHttpRequest();
    this.requests = [];
    this.xhr.onCreate = function (xhr) {
      console.log('request found', xhr);
      this.requests.push(xhr);
      console.log('request pushed');
    };

    this.store = new JSData.DS();
    this.store.registerAdapter('http', new DSHttpAdapter(), { default: true } );
    this.User = this.store.defineResource('user');
  });

  afterEach(function() {
    // Restore the global timer functions to their native implementations
    this.xhr.restore();
  });

  it("User resource", function(done) {
    this.User.find(1).then(function(data) {
        console.log('RESPONSE');
        console.log(data);
        expect(data).to.exist;
        expect(data.id).to.equal(1);
        done();
    });

    setTimeout(() => {
        console.log('responding...');
        this.requests[0].respond(200, { "Content-Type": "application/json" }, JSON.stringify({id: 1, name: 'Sean'}));
    }, 100);
  });
});

and the output:

INFO: null, 'new data store created', Defaults{}, null, null
INFO: null, 'registerAdapter', 'http', DSHttpAdapter{defaults: Defaults{log: function (a, b) { ... }, error: function (a, b) { ... }}}, Object{default: true}
INFO: null, 'default adapter is http', null, null, null
INFO: 'user', 'Preparing resource.', null, null, null
INFO: 'user', 'Done preparing resource.', null, null, null
INFO: 'user', 'find', 1, Defaults{}, null
INFO: null, 'getAdapter', Defaults{}, null, null
LOG: 'request found', FakeXMLHttpRequest{readyState: 0, requestHeaders: Object{}, requestBody: null, status: 0, statusText: '', upload: UploadProgress{eventListeners: Object{progress: ..., load: ..., abort: ..., error: ...}}, responseType: '', response: '', withCredentials: false, eventListeners: Object{loadend: [...], abort: [...], load: [...], loadstart: [...]}}
ERROR: 'Potentially unhandled rejection [5] TypeError: Cannot read property 'method' of undefined
    at logResponse (http://localhost:9876/base/jspm_packages/npm/js-data-http@1.2.3/dist/js-data-http.js:150:73)
    at O (http://localhost:9876/base/jspm_packages/es6-module-loader.js?7c8d992be9f4d67806fc861d6662acb5c4ad5c24:7:7439)
    at K (http://localhost:9876/base/jspm_packages/es6-module-loader.js?7c8d992be9f4d67806fc861d6662acb5c4ad5c24:7:7071)
    at z.when (http://localhost:9876/base/jspm_packages/es6-module-loader.js?7c8d992be9f4d67806fc861d6662acb5c4ad5c24:7:10953)
    at x.v.run (http://localhost:9876/base/jspm_packages/es6-module-loader.js?7c8d992be9f4d67806fc861d6662acb5c4ad5c24:7:9781)
    at a._drain (http://localhost:9876/base/jspm_packages/es6-module-loader.js?7c8d992be9f4d67806fc861d6662acb5c4ad5c24:7:1740)
    at drain (http://localhost:9876/base/jspm_packages/es6-module-loader.js?7c8d992be9f4d67806fc861d6662acb5c4ad5c24:7:1394)
    at MutationObserver.b (http://localhost:9876/base/jspm_packages/es6-module-loader.js?7c8d992be9f4d67806fc861d6662acb5c4ad5c24:7:3302)'
LOG: 'responding...'
  JSData Suite
    ✖ User resource

Finished in 0.424 secs / NaN secs

SUMMARY:
✔ 0 tests completed
✖ 1 tests failed

FAILED TESTS:
  JSData Suite
    ✖ User resource
      Chrome 43.0.2357 (Mac OS X 10.10.3)
    Uncaught TypeError: Cannot read property 'respond' of undefined (/Users/smlynch/Development/playground/js-data-test/test/simple.test.js!eval:48)
    Error: Uncaught TypeError: Cannot read property 'respond' of undefined (base/test/simple.test.js!eval:48)

Here is my package.json:

{
  "jspm": {
    "directories": {},
    "dependencies": {
      "js-data": "npm:js-data@1",
      "js-data-http": "npm:js-data-http@1",
      "js-data-schema": "npm:js-data-schema@1",
      "sinon": "npm:sinon@^1.15.3"
    },
    "devDependencies": {
      "babel": "npm:babel-core@^5.1.13",
      "babel-runtime": "npm:babel-runtime@^5.1.13",
      "core-js": "npm:core-js@^0.9.4"
    }
  },
  "devDependencies": {
    "karma": "^0.12.36",
    "karma-chrome-launcher": "^0.1.12",
    "karma-jasmine": "^0.3.5",
    "karma-mocha": "^0.1.10",
    "karma-mocha-reporter": "^1.0.2",
    "karma-sinon-chai": "^1.0.0",
    "mocha": "^2.2.5"
  }
}

The primary error seems to come from this.requests.push(xhr); causing the error:

ERROR: 'Potentially unhandled rejection [5] TypeError: Cannot read property 'method' of undefined
    at logResponse (http://localhost:9876/base/jspm_packages/npm/js-data-http@1.2.3/dist/js-data-http.js:150:73)
...

I'm using js-data* 1.x as the 2.x betas (which are installed by default off npm if I didn't add @1) were giving me an error on new DSHttpAdapter() if I didn't pass an empty options object (ie. new DSHttpAdapter({}))

techniq commented 9 years ago

I was starting this test to understand how to transform data from a http request (basically how dates using ISO strings are handled. Are they still strings, or converted to Date instances? I've had the need to modify these timestamps on occasion as was kicking the tires of js-data. I currently have a custom Angular data-layer / services that do similar what js-data does with regards to caching, but I liked the more standard/non-angular approach js-data has taken.

techniq commented 9 years ago

Hmm, the issue doesn't appear to be specific to jspm / es6 modules after all. I setup an environment using only npm / CommonJS / mocha (instead of karma) and am still getting the same error from js-data-http

test.js

var JSData = require('js-data');
var DSHttpAdapter = require('js-data-http');
var sinon = require('sinon');

describe("JSData Suite", function() {
  beforeEach(function() {
    this.xhr = sinon.useFakeXMLHttpRequest();
    this.requests = [];
    this.xhr.onCreate = function (xhr) {
      console.log('request found', xhr);
      this.requests.push(xhr);
      console.log('request pushed');
    };

    this.store = new JSData.DS();
    this.store.registerAdapter('http', new DSHttpAdapter(), { default: true } );
    this.User = this.store.defineResource('user');
  });

  afterEach(function() {
    // Restore the global timer functions to their native implementations
    this.xhr.restore();
  });

  it("User resource", function(done) {
    this.User.find(1).then(function(data) {
        console.log('RESPONSE');
        console.log(data);
        expect(data).to.exist;
        expect(data.id).to.equal(1);
        done();
    });

    setTimeout(function() {
        console.log('responding...');
        this.requests[0].respond(200, { "Content-Type": "application/json" }, JSON.stringify({id: 2, name: 'Sean'}));
    }, 400);

  });
});

error

Unhandled rejection TypeError: Cannot read property 'method' of undefined
    at logResponse (/Users/smlynch/Development/playground/js-data-test/node_modules/js-data-http/dist/js-data-http.js:184:68)

package.json

{
  "devDependencies": {
    "mocha": "^2.2.5",
    "sinon": "^1.15.3"
  },
  "dependencies": {
    "js-data": "^1.8.0",
    "js-data-http": "^1.2.3"
  }
}
jmdobry commented 9 years ago

I think I know what the error is, unfortunately I'm extremely busy at work the next 24 hours, so I'll have to fix it over the weekend. If you want to submit a pull request there needs to be a null check here: https://github.com/js-data/js-data-http/blob/master/src/index.js#L101

techniq commented 9 years ago

Sorry about the noise. Turns out the issue was a syntax error in hiding.

    this.requests = [];
    this.xhr.onCreate = function (xhr) {
      this.requests.push(xhr);
    };

should have been (using ES6 arrow function)

    this.requests = [];
    this.xhr.onCreate = (xhr) =>  this.requests.push(xhr);

or (ES5)

    var requests = this.requests = [];
    this.xhr.onCreate = function (xhr) {
      requests.push(xhr);
    };

Odd error to be raised due to this.requests === undefined. Thanks for getting back with me. Now I can get back to evaluating JSData for my needs :)