Mocking and Stubbing with protractor
Asked Answered
A

8

33

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.

Aldrich answered 27/9, 2013 at 17:4 Comment(1)
Did you find a solution in the meantime? I got kind of same issue, see #21727553Trescott
G
10

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.

Gordie answered 3/5, 2014 at 2:38 Comment(0)
K
4

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

Kimble answered 21/8, 2014 at 2:51 Comment(0)
M
2

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);
});
Militarism answered 27/3, 2014 at 9:17 Comment(3)
I'm currently attempting to do this in my e2e test file, but I don't know how to get a hold of $httpBackend. Because inject() is not available in protractor.Syncope
This won't work, you dont' have access to $httpBackend in protractor specKimble
This works only if you have setup your html to have a ng-app that points to the mocked module. Not a solution.Headwaiter
C
2

I know of two such mocking frameworks which can help you. One is ng-apimock and another is json-server.

I have recently started using ng-apimock API for mocking some backend REST calls. It seems good so far as I am able to see some interesting functions available in this npm library. Here, you can define and select Scenarios and Presets(multiple mocks) and basically configure which mock to use for which e2e test case. This basically means a fine-grained control over the e2e tests by providing essential response data as required. It is not eas to set-up as many blogs say on the internet, this I can definitely confirm. But it looks like the solution for my use-case.

I basically had to setup up a proxy.conf.json in addition to defining mocks, and presets(optional), and had to maintain some protractor configuration also for working with this API.

Basically, you can configure exactly what values should be returned from which API endpoint on e2e test run, and along with it, can even disable change what values should be returned for each e2e test case also. There's one option called as passThrough also in this API, which means you can even choose that scenario to make sure the mocks are disabled and calls go to your real HTTP backend.

If you want more details, let me know and I can probably provide you with details on how to configure it.


UPDATE(LONG POST ALERT!!) :

Mock Server Setup(ng-apimock & protractor with express, concurrently)

mock-server.js

const express = require('express');
const apimock = require('@ng-apimock/core');
const devInterface = require('@ng-apimock/dev-interface');
const app = express();

app.set('port', (process.env.PORT || 30020));

apimock.processor.process({
    src: 'mockapi',              //name of the folder containing mocks and presets
    watch: true
});

app.use(apimock.middleware);
app.use('/mocking', express.static(devInterface));      //endpoint for UI Dev Interface

app.listen(app.get('port'), function() {
    console.log('mock app-server running on port', app.get('port'));
});

package.json(scripts section)

    "test:e2e": "ng e2e",
    "start:mockapi": "node mockapi/mock-server.js",
    "e2e": "concurrently -k -s first \"npm run start:mockapi\" \"npm run test:e2e\""

package.json(devDependencies section)

    "@ng-apimock/core": "^2.6.0",
    "@ng-apimock/dev-interface": "^1.1.0",
    "@ng-apimock/protractor-plugin": "^1.1.0",
    "ng-apimock": "^1.4.9",
    "concurrently": "^6.0.1",
    "express": "^4.17.1",

mock-proxy.conf.json A needed proxy configuration file was added inside mocks folder. This is to ensure that the proxy http backend calls go to the correct mock server Url.

Additional endpoint is added for the UI Dev Interface which can be used during development for manual configuration of scenarios and other details related to the defined mocks. After starting mock api server, localhost:30020/mocking can be launched. All to passThrough Option can be selected from UI if we want all mock scenarios to be disabled, and the call will go to the actual REST backend app-server.

{
    "/api/*": {
        "target": "http://localhost:30020",
        "secure": false,
        "logLevel": "debug"
    },
    "/application-server/*": {
        "target": "http://localhost:30020",
        "secure": false,
        "logLevel": "debug"
    },
    "/ngapimock/*": {
        "target": "http://localhost:30020",
        "secure": false,
        "logLevel": "debug"
    },
    "/mocking/*": {
        "target": "http://localhost:30020",
        "secure": false,
        "logLevel": "debug"
    }
}

((Note: our Dev App Server generally runs on 30020))

Protractor Configuration

ngApiMock related options pertaining to Angular version, protractor plugin package and to use custom global ngApiMock client name(to be the only name declaration for use in e2e specs for API method calls) were added in protractor.conf.js.

options: {
            globalName: 'ngApiMockClient',  //this name should be used for declaration of Client in e2e tests
        }

Below Options were removed:

useAllAngular2AppRoots → removed to avoid conflict with the version specification of Angular which comes with ng-apimock protractor options.

baseUrl → removed to avoid conflicts with the proxy configuration that is described in the next step.

angular.json Changes
Addition of a new Webpack DevServer target serve-e2e for pointing to the mock-proxy.conf.json. This target is then called in place of regular "serve" target for launching the application during a typical E2E tests run. This new target addition ensures that we don't use the proxy configuration for the general application launch triggered on "ng serve" which is used often during development.

            "serve": {
                "builder": "@angular-devkit/build-angular:dev-server",
                "options": {
                    "browserTarget": "tnps-portal:build"
                },
                "configurations": {
                    "production": {
                        "browserTarget": "tnps-portal:build:production"
                    }
                }
            },
            "serve-e2e": {
                "builder": "@angular-devkit/build-angular:dev-server",
                "options": {
                    "browserTarget": "tnps-portal:build",
                    "proxyConfig": "mockapi/mock-proxy.conf.json"
                },
                "configurations": {
                    "production": {
                        "browserTarget": "tnps-portal:build:production"
                    }
                }
            },

and specifying serve-e2e as e2e devServer target...

            "e2e": {
                "builder": "@angular-devkit/build-angular:protractor",
                "options": {
                    "protractorConfig": "e2e/protractor.conf.js",
                    "devServerTarget": "tnps-portal:serve-e2e"
                },
                "configurations": {
                    "production": {
                        "devServerTarget": "tnps-portal:serve:production"

E2E Usage

All the mock files should be declared as *.mock.json and presets as *.preset.json before starting the mock api server, for processing of all mocks and presets. This default configuration can be modified by specifying regex pattern of mocks and presets in the mock-server.js file, e.g. it can be made *Mock.json & *Preset.json

import { Client } from '@ng-apimock/base-client';
declare const ngApiMockClient: Client;  // !IMPORTANT: the name of the constant should match the global name defined in protractor.conf.js


it('sample mock test', () => {
ngApiMockClient.selectScenario('sample-mock-name', 'sample-scenario-name',);// sample-mock-name is the name of the mock, and sample-scenario-name is the response scenario name as defined in some sample.mock.json

The above code should choose scenario for a specific mock, which basically means some specific data can be returned for some specific use-cases. This data is also defined in sample.mock.json under responses as follows -

"name": "sample-mock-name",
"isArray": true,                                                                                
"request": {
        "url": "application-server/actual-endpoint-name",   // Endpoint Url for the Mock Request
        "method": "GET"                    // HTTP call type - GET, POST, etc.
    },
"responses": {
        "fileDataScenario": {
            "file": "../data/sampleData.json",   // returns json Array data from a file
            "default": false
        },
        "emptyListScenario": {
            "data": [{}],                       // returns data as array, "isArray" : true mandatory for the same mock.
            "default": true                     // this scenario's data will be returned if no scenario is selected from E2E Code or /mocking UI.
        }

Selecting scenario will suffice for testing simple use-cases. For more complex cases where you need to make sure to return specific scenarios from some specific mocks during runtime, please configure presets as below -

{
    "name": "sample-preset",
    "mocks": {
        "sample-mock-name": {
            "scenario": "fileDataScenario",
            "delay": 3000
        },
        "sample-mock-name-2": {
            "scenario": "someOtherScenarioFromMock2",
            "delay": 3000
        },
        "sample-mock-name-3": {
            "scenario": "oneMoreScenarioFromMock3",
            "delay": 3000
        }

    },
    "variables": {
        "something": "awesome"
    }
}

and in e2e specs

ngApiMockClient.selectPreset('sample-preset');

Above code blocks describe some common examples which might be useful for mocking REST calls for E2E tests with protractor and ng-apimock.

ng-apimock Docs

Cheremkhovo answered 18/4, 2021 at 13:9 Comment(2)
you can provide useful details about configuration right here like the other people answered and few code snippets. not later. it'll be useful for community if you did.Bookplate
That's a good idea, thanks. Updated the answer!Cheremkhovo
L
1

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

Lubeck answered 10/4, 2014 at 19:28 Comment(3)
Is this still maintained?Heddy
Yes, I'm preparing a new release to unify unit and e2e mocking data.Lubeck
For the record, the module has since been deprecated by its author.Uriiah
J
1

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');
    }
};
Jacaranda answered 13/7, 2014 at 10:30 Comment(0)
I
0

The point of running end to end tests with protractor is to validate that the application works in integration. If you're trying to test your ui elements in isolation, it's easier to use small elements from normal tests. Exactly like AngularJS itself tests directives.

That said, if you really want to mock, one way is to create a separate build of your application with stubs instead of real services.

Individualism answered 21/3, 2014 at 19:29 Comment(0)
E
0

Here are a few more options to stub HTTP server:

  • Stubby a small web server with support of node, .Net and Java. You will need it to install and host yourself.
  • Apiary a hosted service to create fake API. You can use it to create API documentation as well.
Endstopped answered 15/7, 2014 at 15:23 Comment(1)
I would like to add Stubby DB in your list which is based on node and capable to serve static and fully dynamic response.Manful

© 2022 - 2024 — McMap. All rights reserved.