Mocking and Stubbing with protractor

23,019

Solution 1

This blog post discusses advance usage scenarios for Protractor. In particular it covers the the little know addMockModule() method of the Protractor browser object. The method allows you to create angular modules in Protractor (i.e. mocks or stubs of your API module) and upload them to the browser to replace the real implementation within the context of a given spec or set of specs.

Solution 2

You do not have access to $httpBackend, controllers or services from within a protractor test so the idea is to create another angular module and include it in the browser during the test.

  beforeEach(function(){
    var httpBackendMock = function() {
      angular.module('httpBackendMock', ['ngMockE2E', 'myApp'])
        .run(function($httpBackend) {
          $httpBackend.whenPOST('/api/packages').respond(200, {} );
        })
    }
    browser.addMockModule('httpBackendMock', httpBackendMock)
  })

ngMockE2E allows you to create a fake backend implementation for your application. Here is a more in depth post on the topic http://product.moveline.com/testing-angular-apps-end-to-end-with-protractor.html

Solution 3

Although I've not tried it myself at this point, Angular provides a mock $httpBackend for E2E tests:

http://docs.angularjs.org/api/ngMockE2E/service/$httpBackend

So, taking from the above docs page, I suspect you can use something like the following before your tests

beforeEach(function() {
  $httpBackend.whenGET('/remote-url').respond(edgeCaseData);
});

Solution 4

I created a little customizable mock module to help me handle success and error scenarios, maybe it will help you better organize mocking.

https://github.com/unDemian/protractor-mock

Solution 5

I've been trying to mock some services in protractor, and after looking some blogs I've arrived to a solution that works for me. The idea is not to do heavy mocking, just generate some error responses; since for the fixtures I already have a backdoor in my API server to populate the backend.

This solution uses the $provide.decorator() to just alter some methods. Here how it's used in the tests:

it('should mock a service', function () {
    app.mock.decorateService({
        // This will return a rejected promise when calling to "user"
        // service "login()" method resolved with the given object.
        // rejectPromise() is a convenience method
        user: app.mock.rejectPromise('login', { type: 'MockError' }),

        // You can decorate the service
        // Warning! This code get's stringified and send to the browser
        // it does not have access to node
        api: function ($delegate, $q) {
            $delegate.get = function () {
                var deferred = $q.defer();

                deferred.resolve({ id: 'whatever', name: 'tess' });

                return defer.promise;
            };

            return $delegate;
        },

        // Internally decorateService converts the function to string
        // so if you prefer you can set an string. Usefull for creating your
        // own helper methods like "rejectPromise()".
        dialog: [
            "function ($delegate, $window) {",
                "$delegate.alert = $window.alert;",
                "return $delegate;",
            "}"
        ].join('\n')
    });

    // ...

    // Important!
    app.mock.clearDecorators();
});

Here the code:

App.prototype.mock = {
    // This must be called before ".get()"
    decorateService: function (services) {
        var code = [
            'var decorer = angular.module("serviceDecorator", ["visitaste"]);',
            'decorer.config(function ($provide) {'
        ];

        for (var service in services) {
            var fn = services[service];

            if (_.isFunction(fn)) {
                code.push('$provide.decorator("'+ service +'", '+ String(fn) +');');
            } else if (_.isString(fn)) {
                code.push('$provide.decorator("'+ service +'", '+ fn +');');
            }
        }

        code.push('});');

        browser.addMockModule('serviceDecorator', code.join('\n'));
    },
    clearDecorators: function () {
        browser.clearMockModules();
    },
    rejectPromise: function (method, error, delay) {
        return [
            'function ($delegate, $q) {',
                '$delegate.'+ method +' = function () {',
                    'var deferred = $q.defer();',
                    '',
                    'setTimeout(function () {',
                        'deferred.reject('+ JSON.stringify(error) +');',
                    '}, '+ (delay || 200) +');',
                    '',
                    'return deferred.promise;',
                '};',
                '',
                'return $delegate;',
            '}'
        ].join('\n');
    }
};
Share:
23,019
Admin
Author by

Admin

Updated on July 16, 2022

Comments

  • Admin
    Admin almost 2 years

    I want to test my angular app with protractor. The app has an API Module that talks to the server During these tests I want to mock this Api Module. I don't want to do full integration tests, but tests from the user input with expected values from the API. Not only could this make the client tests faster, it would also allow me to test for edge cases, like connection errors.

    How can I do this with protractor? I just started to setup integration tests.

    I used the npm protractor module, installed selenium, adjusted the default config and used the onProtractorRunner.js to verify my setup works.

    What is the recommended way of mocking? I assume that the mocking has to be done within the browser and not directly in the test file. I assume that the commands in the test file are protractor specific and will be sent to the selenium runners. Therefore I can't share javascript objects during the session and the test.

    I somehow expect that I will need a spy library like sinon.js or is this already included in protractor?

    Edit: I read about this issue in the protractor issue tracker, which could be a way to do it. Basically you write a Mock Module in the test, that is sent to be executed in the browser/ the applications scope.

    Edit: Here are more promising Issues. The first talks about adding Mocks to the Angular App. The second talks about mocking the backend.

    This looks really nice, in this case the Angular App would stay in it's original form. However this currently only works with the deprecated ng-scenarios.