how to mock the backend for angularjs app?

那年仲夏 提交于 2019-12-22 10:45:07

问题


I would like to test a factory method that runs $resource. My test would mock the real backend that does not exist yet.

Here is a sample factory code:

app.factory( 'Global', function( $resource ){
  var Jogas = $resource('/jogasok/:id', {'id': '@id'}, {
    'ujBerlet': {'method': 'POST', 'params': {'berlet': true}}
  });
  var jogasok = Jogas.query();
  return {
    getJogasok: function() {
      return jogasok;
    }
  };
})

My test would be to check that query was made.

If I initialize my app with:

app.run( function run ($httpBackend) {
  $httpBackend.whenGET('/jogasok').respond([
        {id: 1, name: 'asdfasdf'}, 
        {id: 2, name: '2wrerwert'}
      ]);
})

and open the app in my browser, then everything seems to be fine, I have the dummy data in the browser.

But, when I take out the run code above, and write a test, things just don't work.

describe( 'Global Service: ', function() {

  beforeEach( module('bkJoga') );
  beforeEach( inject(function($httpBackend) {
    $httpBackend.whenGET('/jogasok').respond([
      {id: 1, name: 'asdfasdf'}, 
      {id: 2, name: '2wrerwert'}
    ]);
  }));

  it('getJogasok should return everyone', inject(function(Global) {
    expect(JSON.stringify(Global.getJogasok())).toBe(JSON.stringify([
      {id: 1, name: 'asdfasdf'}, 
      {id: 2, name: '2wrerwert'}
    ]));
  }));
});

fails.


回答1:


Here is a working test for the factory as you posted it. I added a variable $httpBackend for the injected httpBackend. And a call to $httpBackend.flush(). fiddle-demo (read to the end for full description of fiddle content)

describe( 'Global Service: ', function() {
    var Global, $httpBackend

    // load the relevant application module, with the service to be tested
    beforeEach( module('bkJoga') );

    beforeEach( function() {

        // inject the mock for the http backend
        inject(function(_$httpBackend_) {
            $httpBackend = _$httpBackend_;
        });
        // mock the response to a particular get request
        $httpBackend.whenGET('/jogasok').respond([
            {id: 1, name: 'asdfasdf'}, 
            {id: 2, name: '2wrerwert'}
        ]);
        // inject the service to be tested
        inject(function(_Global_) {
            Global = _Global_;
        });
    });

    it('should exist', function() {
        expect(!!Global).toBe(true);
    });

    it('getJogasok should return everyone', function() {
        $httpBackend.flush();    // <------------ need to flush $httpBackend
        expect(JSON.stringify(Global.getJogasok())).toBe(JSON.stringify([
            {id: 1, name: 'asdfasdf'}, 
            {id: 2, name: '2wrerwert'}
        ]));
    });
});

In any event, I would rewrite the factory a bit differently, because, as it is currently written, it queries the database only upon instantiation var jogasok = Jogas.query();. Because services in angularjs are singletons, in your app you will have only the data as they are at the time of instantiation. Therefore, later modifications to the data will not be reflected in your factory. Here is an example of the factory and its unit-test that reflects this idea.

the factory:

app.factory('GlobalBest', function ($resource) {
    return $resource('/jogasok/:id', {
        'id': '@id'
    }, {
        'ujBerlet': {
            'method': 'POST',
                'params': {
                'berlet': true
            }
       }
    });
});

the test:

describe('GlobalBest Service: ', function () {
    var srv, $httpBackend;

    beforeEach(module('bkJoga'));

    beforeEach(function () {
        // inject the mock for the http backend
        inject(function (_$httpBackend_) {
            $httpBackend = _$httpBackend_;
        });

        // inject the service to be tested
        inject(function (_GlobalBest_) {
            srv = _GlobalBest_;
        });
    });

    it('should exist', function () {
        expect( !! srv).toBe(true);
    });

    it('query() should return everyone', function () {

        // mock the response to a particular get request
        $httpBackend.whenGET('/jogasok').respond([{
            id: 1,
            name: 'asdfasdf'
        }, {
            id: 2,
            name: '2wrerwert'
        }]);

        // send request to get everyone
        var data = srv.query();

        // flush the pending request
        $httpBackend.flush();
        expect(JSON.stringify(data)).toBe(JSON.stringify([{
            id: 1,
            name: 'asdfasdf'
        }, {
            id: 2,
            name: '2wrerwert'
        }]));
    });

    it('get({id: 1}) should return object with id=1', function () {
        // mock the response to a particular get request
        $httpBackend.whenGET('/jogasok/1').respond({
            id: 1,
            name: 'asdfasdf'
        });
        var datum = srv.get({
            id: 1
        });
        $httpBackend.flush();
        expect(JSON.stringify(datum)).toBe(JSON.stringify({
            id: 1,
            name: 'asdfasdf'
        }));
    });
});

I wrote a fiddle-demo with 3 versions of the service: your original service 'Global', a new version 'GlobalNew' that returns the query() method, and finally a version 'GlobalBest' that returns directly the $resource. Hope this helps.




回答2:


try changing it from .toBe to .toEqual. Jasmine does object reference equality with toBe, and deep comparison with toEqual.




回答3:


Until now the best answer I could get was to create another app, that inherits my app + uses the ngMockE2E service. This way it should be possible to mock out these requests.

note: unfortunately, I could not get it working yet with my testing environment




回答4:


Use angular.mock.inject to create the mocked the instance of the service and then you need call flush() of the mock $httpBackend, which allows the test to explicitly flush pending requests and thus preserving the async api of the backend, while allowing the test to execute synchronously.

describe('Global Service: ', function () {
    var srv;

    beforeEach(module('bkJoga'));

    beforeEach(function () {
        angular.mock.inject(function ($injector) {
            srv = $injector.get('Global');
        });
    });

    beforeEach(inject(function ($httpBackend) {
        $httpBackend.flush();
        $httpBackend.whenGET('/jogasok').respond([{
            id: 1,
            name: 'asdfasdf'
        }, {
            id: 2,
            name: '2wrerwert'
        }]);
    }));

    it('getJogasok should return everyone', inject(function (Global) {
        expect(JSON.stringify(Global.getJogasok())).toBe(JSON.stringify([{
            id: 1,
            name: 'asdfasdf'
        }, {
            id: 2,
            name: '2wrerwert'
        }]));
    }));
});

Demo




回答5:


A nice article on AngularJS testing that touches on backend mocks too: http://nathanleclaire.com/blog/2013/12/13/how-to-unit-test-controllers-in-angularjs-without-setting-your-hair-on-fire/



来源:https://stackoverflow.com/questions/20412192/how-to-mock-the-backend-for-angularjs-app

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!