Coder Social home page Coder Social logo

protractor-http-mock's Introduction

Protractor Mock

A NodeJS module to be used alongside Protractor to facilitate setting up mocks for HTTP calls for the AngularJS applications under test.

This allows the developer to isolate the UI and client-side application code in our tests without any dependencies on an API.

This plugin does not depend on Angular Mocks (ngMockE2E) being loaded into your app; therefore, there is no need to modify anything within your current Angular web application.

Build Status

Installation

npm install protractor-http-mock --save-dev

Configuration

In your protractor configuration file, we can set the following options:

Mocks

We can set a collection of default mocks to load for every test, and the name of the folder where your mocks will reside. More on this later.

mocks: {
	default: ['mock-login'], // default value: []
	dir: 'my-mocks' // default value: 'mocks'
},

Directories and file names

We can also configure our root directory where the mocks and protractor configuration will be located; as well as, the name of the protractor configuration file.

onPrepare: function(){
	require('protractor-http-mock').config = {
		rootDirectory: __dirname, // default value: process.cwd()
		protractorConfig: 'my-protractor-config.conf' // default value: 'protractor-conf.js'
	};
}

Usage

Mocks are defined as JSON objects describing the request and response for a particular HTTP call:

  {
	request: {
      path: '/users/1',
      method: 'GET'
    },
    response: {
      data: {
        userName: 'pro-mock',
        email: '[email protected]'
      }
    }
  }

And then set the mock at the beginning of your test before your application loads:

var mock = require('protractor-http-mock');
...

  mock([{
    request: {
      path: '/users/1',
      method: 'GET'
    },
    response: {
      data: {
        userName: 'pro-mock',
        email: '[email protected]'
      }
    }
  }]);

Make sure to clean up after test execution. This should be typically done in the afterEach call to ensure the teardown is executed regardless of what happens in the test execution:

afterEach(function(){
  mock.teardown();
});

Please note that the mock() function needs to be called before the browser opens. If you have different mock data for different tests, please make sure that, either the tests always start in a new browser window, or that its possible to setup all the mocks for each test case before any of tests start running.

Mock files

Mocks can also be loaded from physical files located in the mocks.dir directory that we defined in our configuration:

tests
    e2e
      protractor.conf.js
      mocks
        users.js
      specs
        ...

You could simply load your mocks as follows:

mock(['users']);

Files are structured as standard node modules. The strings passed are the path of the file relative to the mocks directory - the same as if you would be doing a standard require() call.

module.exports = { ... }; // for a single mock.

or

module.exports = [ ... ]; // for multiple mocks.

Schema

The full GET schema for defining your mocks is as follows:

  request: {
    path: '/products/1/items',
    method: 'GET',
		regex: false, // Boolean to enable Regular Expression matching on path. This is an optional field.
    params: { // These match params as they would be passed to the $http service. This is an optional field.
      page: 2,
      status: 'onsale'
    },
    queryString: { // These match query string parameters that are part of the URL as passed in to the $http service. This is an optional field.
      param1: 'My first qs parameters',
      param2: 'Second parameter'
    },
    headers: { //These match headers as the end result of the expression provided to the $http method.
    	head1: 'val1',
    	head2: 'val2'
    }
  },
  response: {
  	data: {}, // This is the return value for the matched request
    status: 500 // The HTTP status code for the mocked response. This is an optional field.
    delay: 2 // number of milliseconds to delay sending back the response.
  }

A full mock for a POST call takes the following options:

  request: {
    path: '/products/1/items',
    method: 'POST',
		regex: false, // Boolean to enable Regular Expression matching on path. This is an optional field.
    data: { // These match POST data. This is an optional field.
      status: 'onsale',
      title: 'Blue Jeans',
      price: 24.99
    }
  },
  response: {
    data: { // This is the return value for the matched request
      status: 'onsale',
      title: 'Blue Jeans',
      id: 'abc123',
      price: 24.99
    },
    status: 204 // The HTTP status code for the mocked response. This is an optional field.
  }

PUT, DELETE, HEAD, PATCH, and JSONP methods are also supported. Please see the examples in the source code for more information.

Request

Defining params, queryString, headers, or data will help the plugin match more specific responses but neither is required. Both correspond to their matching objects as they would be passed into the $http object call.

Headers must be defined as the headers that will be used in the http call. Therefore, if in the code to be tested, the headers are defined using properties with function values, these functions will be evaluated as per the $http specification and matched by end result.

Response

The default status value is 200 if none is specified.

An optional delay value can be set on the response to assert any state that occurs when waiting for the response in your application, i.e. loading messages or spinners. Please note that UI tests with timing expectations can be somewhat unstable and provide inconsistent results. Please use this feature carefully.

Precendence

protractor-http-mock will respond with the last matched request in case there are several matches. The plugin will start matching the default mocks first, followed by those added within the test itself in the order they are added. In other words, the last mock defined will always win.

Inspection

For testing or debugging purposes, it is possible to extract a list of http requests. Simply call the requestsMade function as follows:

var mock = require('protractor-http-mock');
...

  expect(mock.requestsMade()).toEqual([
		{ url : '/default', method : 'GET' },
		{ url : '/users', method : 'GET' }
	]);

It is also possible to clear the list of requests with the clearRequests() method.

If you wish to assert anything but the full list of requests, then you can do the following to piece out the information needed on the requests:

mock.requestsMade().then(function(requests){
	expect(requests[1]).toEqual({ url : '/users', method : 'GET' })
});

Runtime mocks

If there is a need to add or remove mocks during test execution, please use the add() and remove() functions:

mock.add([{
	request: {
		path: '/users',
		method: 'GET',
		params: {
			name: 'Charlie'
		}
	},
	response: {
		data: {
			name: 'Override'
		}
	}
}]);

...

mock.remove([{
	request: {
		path: '/users',
		method: 'GET',
		params: {
			name: 'Charlie'
		}
	},
	response: {
		data: {
			name: 'Override'
		}
	}
}]);

These will dynamically modify your current set of mocks, and any new request that happens after that will work with the updated set of mocks. Please note that these functions only work by adding or removing mocks using inline objects. As of now, it is not possible to add or remove mocks using mock files.

Plugins

Plugins can be used to extend the matching functionality of protractor-http-mock. These are separate from protractor plugins.

A plugin can be defined as either an NPM package or a function.

They can be declared in your protractor configuration to be consumed by all your tests:

baseUrl: 'http://localhost:8000/',
specs: [
  'spec/*.spec.js'
],
httpMockPlugins: {
  default: ['protractor-http-mock-sample-plugin']
}

or in each individual test:

mock([
	//mocks go here
], [
	{
		match: function(mockRequest, requestConfig){
			...
		}
	}
]);

Note that in both your protractor configuration and tests, a plugin can be declared as either an npm package name, or definining the object inline.

See this sample plugin for more information.

Defaults

If necessary, default mocks and plugins can be skipped for a particular test simply by passing true at the end of your mock call:

mock(mocks, plugins, true);

Examples

Included in the code base is an extensive list examples on how to use all the features of this plugin. Please take a look if you have any questions.

To run these tests locally, please follow these steps from the root directory:

  1. npm install
  2. npm run webdriver-update
  3. npm run example

protractor-http-mock's People

Contributors

apaatsio avatar atecarlos avatar backuitist avatar brandonroberts avatar crevil avatar henrahmagix avatar jeffsheets avatar kharnt0x avatar mickelus avatar nielssj avatar reactiveraven avatar slawomirdie avatar xemle avatar zbabtkis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

protractor-http-mock's Issues

Query string does not match

I noticed that there is a test for the scenario in which the query parameters are passed to Angular as part of the url, see https://github.com/atecarlos/protractor-http-mock/blob/master/tests/httpMock.test.js#L395.

However, there is also the case where the query parameters are passed as a config object, e.g.

    var config = {
        params: {
            id: 1,
            city: 'ny%26ny'
        }
    };
    http.get('github.com/user/search', config).then(function(response){

It is in this scenario that the query string does not match the expectation.

Steps to Reproduce

Add this mock to tests/httpMock.test.js:

      {
        request: {
          method: 'GET',
          path: '/user/search',
          queryString: {
            id: '1',
            city: 'Chicago'
          }
        },
        response: {
          data: {
            name: 'Superman'
          }
        }
      },

Then, add this test:

  it('matches by query string', function(done){
    var config = {
      params: {
        id: 1,
        city: 'ny%26ny',
      },  
    };  
    http.get('github.com/user/search', config).then(function(response){
      expect(response.data.name).toBe('Carlos QS');
      done();
    });   
  }); 

The test will fail saying:

   X matches by query string
     Expected 'Superman' to be 'Carlos QS'. (1)

Commentary

I wonder if there is a fundamental problem with the approach this library is taking. The mock expectations that the developer defines are meant to match the HTTP request that would go over the wire. However, when protractor-http-mock matches a request against the list of mock requests, it is comparing how $http defines the request versus what would actually be sent over the wire. This issue is an example of that: you can configure query parameters in two ways.

If that's true, I think it would benefit from normalizing Angular's config into something that more closely matches what the true HTTP request would be.

What are your thoughts?

Problem with mock and interceptors

Hi All,
I have problem with using this node library. I seems like every stuff I mocked not going though angular interceptor so results on frontend are different then expected because they are not transform before and after going though interceptor.

So in my angular app I have this interceptor:

return {
      request: function request (config) {
        config = _.assign(config, cfg.httpConfiguration);
        config.headers['Content-Type'] = 'application/json';
        return config;
      },
      response: function response (response) {
        return (response.config['verbose']) ? response.data.content : response;
      }
    };

But when I mocking http backend not picking up this and on frontend I have wrong response

transformResponse executes twice

This is weird I really don't know what's going on and I am going to try to provide as much details as I can muster.

First, this is an Ionic project so that probably matters impact. Second this API call is handled by an ngResource model.

So I am in a protractor test and brower paused at the point before the API call so I can open up Chrome debugger and walk through it.

The first pass calls the transform through here: https://github.com/atecarlos/protractor-http-mock/blob/master/lib/httpMock.js#L75

The second pass calls the transform through here: https://github.com/atecarlos/protractor-http-mock/blob/master/lib/httpMock.js#L78

This is a lot less information than I would like, shot in the dark maybe, I'll keep investigating and see what I can turn up.

Breaks $http.defaults.transformRequest

When using this npm module, $http.defaults is undefined. Therefore calls like:

transformRequest: transformations.concat($http.defaults.transformRequest)

are broken.

Backend not being intercepted

First of all, thank you for such an intuitively designed tool. I foresee this being incredibly useful and a joy to use.

I am unable to get my backend mocked. I have followed the setup instructions, studied the example, and browsed closed issues all with no luck.

  • I have no errors.
  • mock.requestsMade( ) is returning an empty array.
  • I can see my backend is still being hit, and the http requests are not being intercepted.

Can you point me in any direction to debug this? Would pasting some of my config be useful? If there is any more information needed to diagnose the issue please let me know.

Thank you for your time.

No way to mock different response for the same request

I have a scenario where there is a page which loads a default table which has two rows (I am able to mock this request - Lets say request1).So the request1 is going to return with 2 rows. I can add more rows to this table by clicking on "Add data to table" button and when i do that the default table reloads with the newly added rows (lets say i added 2 new rows). In this case the same request (request 1) is called and it should return with a total of 4 rows (2 default and 2 newly added). There is no way to mock this behviour as of now.

intercepting http requests headers

I'm currently using following code in angular to intercept all of my requests and add a header if my "token" is set in the $window.

    $httpProvider.interceptors.push(['$q', '$location', '$window', 'ENV',
        function($q, $location, $window, ENV) {
            return {
                'request': function(config) {
                    if (config.url.indexOf("token") < 0) {
                        if (config.url.startsWith(ENV.apiEndpoint)) {
                            if ($window.sessionStorage.token) {
                                // set this otherwise protractor-http-mock wont work
                                if (config.headers === undefined) config.headers = {};
                                var token = JSON.parse($window.sessionStorage.token);
                                config.headers.Authorization = token.token;
                            } else {
                                $location.path('/login');
                            }
                        }
                    }
                    return config;
                },
                'responseError': function(response) {
                    if (response.status === 401 || response.status === 403) {
                        $location.path('/login');
                    }
                    return $q.reject(response);
                }
            };
        }
    ]);

In my live environment this interceptor automatically adds headers to my request. If I however use your environment the headers Variable aren't automatically added and I have to add it like this

I have to set the headers:
if (config.headers === undefined) config.headers = {};

It took me a while to figure this out but now all of my tests are executed properly.

Requests with FormData

When dealing with requests with FileUploads, it is very common to use FormData.

Unluckily only Firefox 39 has getters to retrieve the values of the keys that have been appended. The rest of the browsers just handle this data internally in the browser. This makes testing almost impossible as you can't read values from the FormData object. I guess this is something that will change sooner or later as I've seen some work from Mozilla that is being tested in Chrome's Experimental Features, but for we are lost.

We have been using protractor-http-mock for a long time, and we could't test requests with FormData, as the data object of every request is always { append : function append() } which makes all matchs fail.

The workaround we found is to modify FormData's prototype, adding a property called: 'representation' which keeps track of all keys that has been appended. This is a sample code just as a proof of concept:

// Save original append function as _append
FormData.prototype._append = FormData.prototype.append;
// Override append function
FormData.prototype.append = function(key, value) {
    // Add a new property to keep track of changes
    if (!this.representation) {
        this.representation = {};
    }
    // Add appended data to representation object
    this.representation[key]` = value;
    // Call original function
    this._append(key, value);
};

So now we can read the data that was added to FormData just by accessing representation property.

With this done I suggest adding this small change to transformRequest method:

if (requestConfig.data instanceof FormData && requestConfig.data.representation) {
    requestConfig.data = requestConfig.data.representation;
}

With this simple code, we can now test requests with FormData.

Any suggestions are welcome!

If you think this is acceptable, please accept my pull request.

mock.requestsMade() is returning empty array

I am currently on version 0.1.8 and tried to upgrade to latest. Noticed after I upgraded, I am now getting an empty array for requests made. I notice the same is happening here:

#27

Thanks.

Module 'httpMock' is not available

I stuck a mock.clearRequests() in my first test inside a describe() to be extra super sure there were no requests pending. I got this error when I ran protractor:

1) Bad logins displays an error on a failed authentication
  Message:
    Failed: javascript error: [$injector:nomod] Module 'httpMock' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
    http://errors.angularjs.org/1.3.14/$injector/nomod?p0=httpMock
    JavaScript stack:
    Error: [$injector:nomod] Module 'httpMock' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
    http://errors.angularjs.org/1.3.14/$injector/nomod?p0=httpMock
        at http://localhost/bower_components/angular/angular.js:63:12
        at http://localhost/bower_components/angular/angular.js:1767:17
        at ensure (http://localhost/bower_components/angular/angular.js:1691:38)
        at Object.module (http://localhost/bower_components/angular/angular.js:1765:14)
        at eval (eval at executeAsyncScript (unknown source), <anonymous>:2:11)
        at eval (eval at executeAsyncScript (unknown source), <anonymous>:5:5)
        at eval (eval at executeAsyncScript (unknown source), <anonymous>:5:31)
        at executeAsyncScript (<anonymous>:321:26)
        at <anonymous>:337:29
        at callFunction (<anonymous>:229:33)
      (Session info: chrome=41.0.2272.118)
      (Driver info: chromedriver=2.14.313457 (3d645c400edf2e2c500566c9aa096063e707c9cf),platform=Mac OS X 10.10.3 x86_64) (WARNING: The server did not provide any stacktrace information)
    Command duration or timeout: 7 milliseconds
    Build info: version: '2.45.0', revision: '5017cb8', time: '2015-02-26 23:59:50'
    System info: host: 'spamguy', ip: '10.30.11.66', os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.10.3', java.version: '1.6.0_65'
    Session ID: 866a45433d15016458b25d91abf50611
    Driver info: org.openqa.selenium.chrome.ChromeDriver
    Capabilities [{platform=MAC, acceptSslCerts=true, javascriptEnabled=true, browserName=chrome, chrome={userDataDir=/var/folders/6s/27x5n63d2_zbnf81_mlf9r3w0000gn/T/.org.chromium.Chromium.nkrCx2}, rotatable=false, locationContextEnabled=true, mobileEmulationEnabled=false, version=41.0.2272.118, takesHeapSnapshot=true, cssSelectorsEnabled=true, databaseEnabled=false, handlesAlerts=true, browserConnectionEnabled=false, webStorageEnabled=true, nativeEvents=true, applicationCacheEnabled=false, takesScreenshot=true}]

I would think that, even if there are no requests archived, mock.clearRequests() would still work.

mocks to a API on another domain

Hey Guys,

I have a quick question, can i mock a PUT request that is being made to an API on another domain. Something like this:

    mock([{
      request: {
        path: 'https://<domain>/v1/auth/forgot_password',
        method: 'PUT'
      },
      response: {
        status: 200
      }
    }]);
  });

This does not appear to be working in my tests, could i replace path with url maybe? Thanks!

misidentifying protractor config file

Somewhat of a protractor newbie so forgive me if this is user error.
My protractor conf filename is "protractor-conf.js".
When I run tests with protractor-http-mock, it looks for the filename "protractor.conf".
I can work around it by hardcoding the filename in line 34 in getMocksConfig() in init-data.js, but that's not viable long-term.

Here's how I run protractor tests:
protractor --specs [path to spec js file] protractor-conf.js
Note that the conf filename is the last argument. Maybe http-mock can pick up the filename from there?

Love the functionality though.

Module 'httpMock' is not available

I'm trying to get a very basic mock setup working but it seems to be skipping the http mock altogether. The browser request executes normally and I get an error when selenium is not running. When I call mock.requestsMade() just to try to see what is happening, I get a Module 'httpMock' is not available error. Any help you can provide would be greatly appreciated.

Config:

require('babel-core/register')({ presets: ['es2015'] });

exports.config = {
  capabilities: {
    browserName: 'chrome'
  },
  specs: ['test/e2e/browser.js'],
  baseUrl: 'http://localhost:8080',
  frameworks: ['mocha', 'chai'],
  mocks: {
    default: ['default'],
    dir: 'test/e2e/http-mocks'
  },
  onPrepare: () => {
    browser.ignoreSynchronization = true;
    require('protractor-http-mock').config = {
      rootDirectory: __dirname,
      protractorConfig: 'protractor.conf.js'
    };
  }
};

Test:

import 'mocha';
const { browser, describe, beforeEach, afterEach, it, $ } = global;
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { expect } from 'chai';
import mock from 'protractor-http-mock';

chai.use(chaiAsPromised);

describe('root route', () => {
  const defaultUrl = '/review/842b0a5f67ff880444737abc2b245517';

  beforeEach(() => {
    mock([{
      request: {
        path: defaultUrl,
        method: 'GET'
      },
      response: {
        data: {
          test: 'hello'
        }
      }
    }]);
  });

  afterEach(() => {
    mock.teardown();
  });

  describe('as a non-android, non-ios device', () => {
    it('should go to main page', () => {
      browser.get(defaultUrl);
      expect(mock.requestsMade()).to.eventually.equal([]);
      expect($('body').getText()).to.eventually.match(/Would you recommend/);
    });
  });
});

Result:

    Failed: javascript error: [$injector:nomod] Module 'httpMock' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
    http://errors.angularjs.org/1.5.0/$injector/nomod?p0=httpMock
    JavaScript stack:
    Error: [$injector:nomod] Module 'httpMock' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
    http://errors.angularjs.org/1.5.0/$injector/nomod?p0=httpMock
        at http://localhost:8080/vendor.js:32:381
        at http://localhost:8080/vendor.js:32:10599
        at t (http://localhost:8080/vendor.js:32:10072)
        at Object.module (http://localhost:8080/vendor.js:32:10384)
        at eval (eval at executeAsyncScript (unknown source), <anonymous>:3:26)
        at eval (eval at executeAsyncScript (unknown source), <anonymous>:6:5)
        at eval (eval at executeAsyncScript (unknown source), <anonymous>:6:31)
        at executeAsyncScript (<anonymous>:321:26)
        at <anonymous>:337:29
        at callFunction (<anonymous>:229:33)
      (Session info: chrome=48.0.2564.103)
      (Driver info: chromedriver=2.20.353124 (035346203162d32c80f1dce587c8154a1efa0c3b),platform=Mac OS X 10.11.3 x86_64) (WARNING: The server did not provide any stacktrace information)
    Command duration or timeout: 11 milliseconds
    Build info: version: '2.48.2', revision: '41bccdd', time: '2015-10-09 19:59:12'
    System info: host: 'Carls-MBP', ip: '192.168.1.74', os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.11.3', java.version: '1.8.0_66'
    Driver info: org.openqa.selenium.chrome.ChromeDriver
    Capabilities [{applicationCacheEnabled=false, rotatable=false, mobileEmulationEnabled=false, chrome={userDataDir=/var/folders/rd/pfxg2wp96hb3z2xlw9p_w5s80000gn/T/.org.chromium.Chromium.9DNDBF}, takesHeapSnapshot=true, databaseEnabled=false, handlesAlerts=true, hasTouchScreen=false, version=48.0.2564.103, platform=MAC, browserConnectionEnabled=false, nativeEvents=true, acceptSslCerts=true, locationContextEnabled=true, webStorageEnabled=true, browserName=chrome, takesScreenshot=true, javascriptEnabled=true, cssSelectorsEnabled=true}]
    Session ID: 516d7da26de522850404096b8971e032

Break to match request.config intercepted and transformed into promise

According to Angular documentation when implementing the request hook in the interceptor you are allowed to return a promise representing the new config object.
https://docs.angularjs.org/api/ng/service/$http#interceptors

And if you return a rejected promise the $http interprets that such request shouldn't even being made, since it failed before going through the interceptors.

Current implementation of httpMock.match doesn't take this scenario into account, yielding to an error at runtime.
https://github.com/atecarlos/protractor-http-mock/blob/master/lib/httpMock.js#L138

I believe httpMock should abort the mock when request is rejected on the interceptors.
Moreover we should confirm the $http behaviour with different interceptor scenario and make sure httpMock does the same.

Is it possible to not mock some of the queries?

Is following possible with protractor-http-mock?

I have test case that makes a bunch of real http queries to back end server. Now I'd like to mock only one of the queries and have all the other request go to real back end server. If I understood protractor-http-mock correctly, it will mock all queries.

Ability to have multiple JSON objects in mock files

Currently we need one json file for every mock object. It would be great if we can also support loading multiple mock objects from a single file in some like

[ {
 request: {}
 response: {}
},
 request: {}
 response: {}
}]

The reason is that we sometime have multiple mocks we need to load in together to support a single test case and they don't mean much if they are alone.

Backend should be mocked just by matching URL and method, data and others optional

When setting up my mocks, if my actual HTTP request includes data and I fail to include the data attribute in the mock request, the entire HTTP request is not intercepted.

mock( [
{
    request:
    {
      path: '/api/user/list',
      withCredentials: true,
      method: 'post',
    },
    ...
] };

This fails to intercept the request and I can see my backend being hit. However...

mock( [
{
    request:
    {
      path: '/api/user/list',
      withCredentials: true,
      method: 'post',
      data:
      {
        listItem:
        {
            name: 'new list item'
        }
    }
    },
    ...
] };

This WILL intercept the request. This was unexpected behavior to me, especially because leaving out `withCredentials` exhibits opposite behavior: the request is mocked.

Part of me feels like the HTTP request should get mocked simply by matching just the URL and method, and everything else is optional.

Regardless, this behavior should be consistent and documented.

Does this make sense?

Multiple test files stops mock interceptor

This may or may not be related to issue #27. I have 2x test files which both rely on the same mock interceptor. Running them one at a time works perfectly, however when I run both in one protractor environment (e.g. protract {conf} --specs 'test/pages/*.coffee') the first test's call is intercepted but the second isn't, it goes to the backend instead.

matchData -> matchProperty

function matchProperty calls angular.equals to do deep comparisons of expectations and config mocks.

Shouldn't the calls have a json.parse ? Otherwise data configs will come through as strings.

angular.equals(expectationRequest[property], JSON.parse(config[property])

$http and $resource GET requests don't match mock

I am not familiar enough with $http, $resource, and protractor-http-mock to know what is happening here, but I will do my best to thoroughly describe the problem and how to reproduce it.

General Problem

The general problem I'm seeing is that sometimes my mock file does not match against the request being made, and sometimes it does match. I've identified four cases that might be useful for discussion.

Requirements

This is all being run using protractor-http-mock 0.1.16 and protractor 2.1.0.

Mock

First, here is my mock file, access-token.json:

{
  "request": {
    "method": "GET",
    "path": "/api/v1.0/access-token",
    "headers": {
      "Authorization": "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
    }
  },
  "response": {
    "access_token": "P6AlGY9SZSUlw3K0bZteiTp32UP-IOYv49aJNDWZbKQ",
    "type": "Bearer",
    "expires_in": 72204,
    "refresh_token": "u8iBLUb6-sPttALigZZRS4wC-uAuICEd3-8iohDXeiU"
  }
}

Example 1: $http matches the mock

$http({
  method: 'GET',
  url: '/api/v1.0/access-token',
  headers: {
    'Authorization': 'Basic dXNlcm5hbWU6cGFzc3dvcmQ='
  }
})

Example 2: $http does not match the mock

$http({
  url: '/api/v1.0/access-token',
  headers: {
    'Authorization': 'Basic dXNlcm5hbWU6cGFzc3dvcmQ='
  }
})

Example 3: $resource matches the mock

var resource = $resource('/api/v1.0/access-token', {}, {
  get: {
    method: 'GET',
    headers: {
      'Authorization': 'Basic dXNlcm5hbWU6cGFzc3dvcmQ='
    },
  },
});
resource.get()

Example 4: $resource does not match the mock

var resource = $resource('/api/v1.0/access-token', {}, {
  get: {
    headers: {
      'Authorization': 'Basic dXNlcm5hbWU6cGFzc3dvcmQ='
    },
  },
});
resource.get()

Methodology for Testing

I determined that the symptom was the existence or non-existence of the "method" property by doing something like the following:

describe('Login', function({
  var mock = require('protractor-http-mock');

  beforeEach(function(){
    mock(['access-token']);
  });

  afterEach(function(){
    mock.teardown();
  });

  it('gets an access token', function(){
    // <logic for logging in goes here>

    browser.manage().logs().get('browser').then(function(browserLog) {
      console.log('\n log: ' + require('util').inspect(browserLog));
    }); 

  });
});

I found that on failure, I would see this in the log:

 log: [ { level: { value: 1000, name: 'SEVERE' },
    message: 'https://app.local/js/vendor.js 108:266 TypeError: Cannot read property \'toLowerCase\' of undefined\n    at match (<anonymous>:413:78)\n    at matchExpectation (<anonymous>:425:20)\n    at httpMock (<anonymous>:466:31)\n    at Object.get (https://app.local/js/app.js:131:9)\n    at Object.AccessTokenRepository.getByLogin (https://app.local/js/app.js:2996:14)\n    at Object.AuthenticationService.login (https://app.local/js/app.js:3504:27)\n    at m.$scope.submitLogin (https://app.local/js/app.js:677:27)\n    at fn (eval at <anonymous> (https://app.local/js/vendor.js:211:346), <anonymous>:2:440)\n    at f (https://app.local/js/vendor.js:251:418)\n    at m.$eval (https://app.local/js/vendor.js:134:398)',
    timestamp: 1438290151051,
    type: '' } ]

That output doesn't make it extremely clear that it has anything to do with httpMock, but I had a hunch. So, in protractor-http-mock/lib/httpMock.js I changed the match method to append mockConfig to the window:

132         function match(config, expectationRequest){
133             if (typeof window.mockConfigs == 'undefined') {
134               window.mockConfigs = [];
135             }
136             if (! config.hasOwnProperty('method')) {
137               window.mockConfigs.push(config);
138             }
139             return  expectationRequest.method.toLowerCase() === config.method.toLowerCase() &&
...

And then added the following lines to my protractor test to print out the value of window.mockConfigs

    browser.driver.executeScript(function() {
      return window.mockConfigs;
    }).then(function(result) {
      console.log(result);
    });

For the times when protractor-http-mock failed, I saw this:

[ { headers: { Authorization: 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=' },
    url: 'https://app.local/api/api/login-token' } ]

Conclusions

Each of these 4 examples result in a successful XHR request. The problem is in protractor-http-mock's ability to match the request due to the method property not being specified.

I'm wondering if Angular's $http layer is just assuming that any request with no method specified is a GET request. If that is the case, I think it would make sense for this library to assume during matching purposes the following rule:

If no method is specified in the request, a mock that does not specify the method and is a match on all other criteria is a match. If no match is found, then a mock that specifies the method as GET and is a match on all other criteria is a match.

Can anyone else reproduce this issue? How do you think this should be resolved? I'm wondering if even just a disclaimer in the README would be sufficient?

interceptor/transformation handling

Guys,

Placeholder for more information but the TL;DR version is I was finding an interceptor that defined both a req.transformResponse override and a response handler would invoke these backwards when using the mocking library.

In all scenarios the transformResponse was getting called before the response handler, but with the library it was getting called backwards.

This may or may not be related to the fact I don't think its handling response handler $promises properly either.

mocks seem not to work after a page refresh/change

I'm using this module to mock the app while testing it with protractor

as far as I've seen, after I login into the app, and arrive at the home page, mocks are no longer working.

any ideas why ? or how can I reinject the mocks ?

thanks

Problem with ngResource

Hey,

looks like mocking http request fails when fired from ngResource. Has anyone tried this and got it working?

I updated example to use ngResource for one of GET requests and test suite goes red, you can see it here ertrzyiks/protractor-http-mock

Interceptor gets a promise when mock is applied.

I get this error when trying to mock my app:

Broken interceptor detected: Config object not supplied in response:

After a little research I found it's this line (thanks loading-bar project for including that check) throwing the error and its because response is a promise.

Using this module to load mocks into an app as an angular module, outside of protractor

We have been using Protractor Http Mock, and we’re happy with it.

However we saw some shortcomings… In particular the mocks will not be used if you refresh the page, or follow a click to a new page.

These both boil down to the use of adding the mocks via Protractor’s addMockModule.

Ideally it would be good if we could load the mocks into the app, before we run the e2e’s. Then we could refresh the page and the same mocks would be used.

I think the module could be broken down better for use in this manner, if this was something you were considering in the future.

I’ve outlined how we approached this problem ourselves, and wondered if there had been any similar thoughts and attempts?

Our ultimate goal was to be able to use mocks loaded into our app as the default mocks. Then use the Protractor Http Mock to override ones where we needed to.

We used a custom mock-module file which we referenced in the html.
Then used grunt to copy your module and add the mocks (example below) to our custom module.

Please see the examples below of the grunt file and the mock-module file. Underneath is a brief summary.

Inside a grunt file

  • require /protractor-http-mock/lib/httpMock
  • execute the exported default function with an empty string for the expectations
  • turn this into a string
  • add the mocks via find and replace of ‘expectations =‘
  • Change the name of the module (so that we can use Protractor Http Mocks to override mocks when we need to)
  • Then add this string to the custom mock-module file mentioned before.

Inside the custom mock-module we immediately invoke the function and added it to the app.

example grunt file

  var httpMock = require(__dirname
      + '/../node_modules/protractor-http-mock/lib/httpMock’)
   , grunt = require('grunt’);

  var customModule = ’src/e2e/mock-module/mocks.js'
    , customModuleContent
    , dest = ‘dest/e2e/mock-module/mocks.js'
    , httpMockOutput = httpMock('')
    , httpMockOutputString = httpMockOutput.toString();

  httpMockOutputString = httpMockOutputString
    .replace('expectations = []', 'expectations = ' + '[' + getMocks() + ']');
  httpMockOutputString = httpMockOutputString
    .replace(/httpMock/g, 'mockHttp');

  customModuleContent = customModule
    .read()
    .replace(/'<protractor-http-mock>'/, httpMockOutputString);

  grunt.file.write(dest, customModuleContent);

example mock-module/mock.js

('<protractor-http-mock>')();

(function addModuleToApp() {
  var app = document.documentElement.getAttribute('ng-app');

  angular.module(app).requires.push('mockHttp');
})();

Method case causes unmached requests

The method that gets specified in the mock file must match the case that is specified in the application.

Angular Request

$http({
  method: 'get',
  url: '/api/v1.0/access-token',
  headers: {
    'Authorization': 'Basic dXNlcm5hbWU6cGFzc3dvcmQ='
  }
})

Mock File

{
  "request": {
    "method": "GET",
    "path": "/api/v1.0/access-token",
    "headers": {
      "Authorization": "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
    }
  },
  "response": {
    "access_token": "P6AlGY9SZSUlw3K0bZteiTp32UP-IOYv49aJNDWZbKQ",
    "type": "Bearer",
    "expires_in": 72204,
    "refresh_token": "u8iBLUb6-sPttALigZZRS4wC-uAuICEd3-8iohDXeiU"
  }
}

Conclusion

I think expecting the writer of the mock to use the same case as is used in the Angular app isn't completely unreasonable, but it should be communicated to the developer that is the case.

What are your thoughts?

Usage questions and clarifications

Hello,

Thanks for this wonderful module. I've been looking for test fixture to test client side code with interactions independent of the backend and this seems to be perfect for it.

However, I have questions regarding its usage (see https://github.com/atecarlos/protractor-http-mock/blob/master/example/spec/requestsMade.spec.js).

  1. get() on line 44 seems to automatically invoke the first mock (on url /users). Shouldn't it just be getting the index.html and require an explicit GET to /users?
  2. Do the unmocked paths (say /unmocked) go to server? Should they return 404 instead?

Change protractor.conf as default

Is this a convention that I don't know about? The protractor examples use conf.js. Would it be alright to make the default something a little more standard (mostly for syntax highlighting).

can protractor-http-mock Https requests

Im trying to hit backend apis using protractor. So, want to know if protractor-http-mock can handle https as well? in the examples i see only http.

goal is to make client side (https) call to server and parse the response. Can you provide with an examples using authentications?

How to mock the data inside an iframe?

Hi, first of all i'd like to thank you for the great job with the module, it is both intuitive and helpful. It was working perfectly till the moment i needed to mock the data i receive within an iframe. When i make a call outside of the iframe everything works like a charm, however the same call made from the iframe tries to fetch the data from the server. What can be the reason for this behaviour? Is there a way to workaround it?

Using the mock file instead of inline mock

When using mock['test'] I am not getting any mocks back but if I have the mock([{...}]); in the spec it works. I have literally cut and pasted the request from the inline to the file....how should the standalone file look?

How I can dynamically reload mocks in the test?

Hi,

Thank you for a great plugin!

How I can dynamically reload mocks in the test?

Example:
I have a list of repositories, that I fetched by url /repositories.
And I want to delete one of them. When I delete repository, my business logic fetches repositories again by the same url: /repositories and expects that data will be updated.

Example:

it('Should delete repository, function () {
  mock([{
    request: {
      path: '/repositories',
      method: 'GET'
    },
    response: {
     data: [{
      name: 'repo1',
      status: 'activated'
     }, {
      name: 'repo2',
      status: 'activated'
     }]
    }
  }]);

  browser.get('/#/repos');

  expect(element(by.id('repo2').getAttribute('status')).toBe('activated');

  //I want dynamically reload mocks with different data here:
  //mock.clearRequests() and mock.teardown() didn't help me
  mock([{
    request: {
      path: '/repositories',  //SAME URL
      method: 'GET'
    },
    response: {
     data: [{
      name: 'repo1',
      status: 'activated'
     }, {
      name: 'repo2',
      status: 'DELETED' //DIFFERENT DATA
     }]
    }
  }]);

  element(by.buttonText('Delete repo2')).click();

  //FAIL, because browser fetches repositories again, but with old data :(
  expect(element(by.id('repo2').getAttribute('status')).toBe('DELETED');
});

Any ideas?
Thanks

Undefined parameters make expectations impossible

I saw this call in devtools:

/api/v1/issues?offset=0

So I created this mock:

{
  method: 'get'
  path: '/api/v1/issues',
  params: { offset: '0' }
}

But it doesn't match the call, after some debugging I found the actual request object is

{
  url: '/api/v1/issues',
  params: { offset: '0', q: undefined }
}

I added the q: undefined property to my expectation object but it doesn't work since the mock expectations are serialized as JSON and JSON doesn't have undefined there's no way to pass a undefined property.

Module 'httpMock' is not available

I'm experiencing a problem testing with this cool library. I'm simply trying to test my login screen. Can anybody help? By the way, running the test environment of this lib does work flawlessly, I'm just experiencing problems with my own tests.

my test:

/* http://docs.angularjs.org/guide/dev_guide.e2e-testing */
var mock = require('../../node_modules/protractor-http-mock');
describe('login screen test', function() {
    beforeEach(function() {
        mock([{
            request: {
                path: /token/,
                method: 'POST',
            },
            response: {
                data: {
                    'hallo': 'dude'
                },
                status: 200
            }
        }]);
        browser.driver.get('http://localhost:8580');
        browser.driver.findElement(by.id('email')).sendKeys('[email protected]');
        browser.driver.findElement(by.id('password')).sendKeys('pushit');
        browser.driver.findElement(by.id('submit')).click();
    });
    it('expects push model to be filled', function() {
        expect(mock.requestsMade()).toEqual([{
            url: /token/,
            method: 'POST'
        }]);
    });
    afterEach(function() {
        mock.teardown();
    });
});

config:

exports.config = {
    allScriptsTimeout: 11000,
    specs: ['e2e/*.js'],
    capabilities: {
        'browserName': 'chrome'
    },
    chromeOnly: true,
    //baseUrl: 'http://localhost:8580/',
    framework: 'jasmine',
    jasmineNodeOpts: {
        defaultTimeoutInterval: 30000
    },
    mocks: {
        dir: 'mocks', // path to directory with mocks
        default: []
    },
    onPrepare: function() {
        require('../node_modules/protractor-http-mock/index').config = {
            rootDirectory: __dirname,
            protractorConfig: "protractor-conf.js", // name of the config here
        };
    }
};

error:

   UnknownError: javascript error: [$injector:nomod] Module 'httpMock' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
http://errors.angularjs.org/1.3.18/$injector/nomod?p0=httpMock
JavaScript stack:
Error: [$injector:nomod] Module 'httpMock' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
http://errors.angularjs.org/1.3.18/$injector/nomod?p0=httpMock
    at http://localhost:8580/bower_components/angular/angular.js:63:12
    at http://localhost:8580/bower_components/angular/angular.js:1778:17
    at ensure (http://localhost:8580/bower_components/angular/angular.js:1702:38)
    at Object.module (http://localhost:8580/bower_components/angular/angular.js:1776:14)
    at eval (eval at executeAsyncScript (unknown source), <anonymous>:2:26)
    at eval (eval at executeAsyncScript (unknown source), <anonymous>:5:5)
    at eval (eval at executeAsyncScript (unknown source), <anonymous>:5:31)
    at executeAsyncScript (<anonymous>:321:26)
    at <anonymous>:337:29
    at callFunction (<anonymous>:229:33)
  (Session info: chrome=43.0.2357.134)
  (Driver info: chromedriver=2.15.322455 (ae8db840dac8d0c453355d3d922c91adfb61df8f),platform=Mac OS X 10.10.5 x86_64) (WARNING: The server did not provide any stacktrace information)
Command duration or timeout: 66 milliseconds
Build info: version: '2.45.0', revision: '5017cb8', time: '2015-02-26 23:59:50'
System info: host: 'NeilMcCauley.local', ip: '192.168.0.12', os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.10.5', java.version: '1.8.0_45'
Driver info: org.openqa.selenium.chrome.ChromeDriver
Capabilities [{applicationCacheEnabled=false, rotatable=false, mobileEmulationEnabled=false, chrome={userDataDir=/var/folders/n7/rh782dqj3zx_3fk60qj0rk400000gn/T/.org.chromium.Chromium.Ouy9Xv}, takesHeapSnapshot=true, databaseEnabled=false, handlesAlerts=true, version=43.0.2357.134, platform=MAC, browserConnectionEnabled=false, nativeEvents=true, acceptSslCerts=true, locationContextEnabled=true, webStorageEnabled=true, browserName=chrome, takesScreenshot=true, javascriptEnabled=true, cssSelectorsEnabled=true}]
Session ID: 0b656ce7fcca8c0da3438508e25028f5
    at Array.forEach (native)
From: Task: Asynchronous test function: it()
Error
    at [object Object].<anonymous> (/Users/NeilMcCauley/Documents/goedle/code/ui/test/e2e/scenarios_login.js:22:5)
    at Object.<anonymous> (/Users/NeilMcCauley/Documents/goedle/code/ui/test/e2e/scenarios_login.js:3:1)

Finished in 3.931 seconds
1 test, 1 assertion, 1 failure

Shutting down selenium standalone server.
[launcher] 0 instance(s) of WebDriver still running
[launcher] chrome #1 failed 1 test(s)
[launcher] overall: 1 failed spec(s)
[launcher] Process exited with error code 1
>>
Warning: Tests failed, protractor exited with code: 1 Use --force to continue.

my node modules:

{
  "version": "0.0.0",
  "private": true,
  "license": "MIT",
  "devDependencies": {
    "bower": "^1.3.1",
    "grunt": "^0.4.5",
    "grunt-cli": "^0.1.13",
    "grunt-contrib-clean": "^0.6.0",
    "grunt-contrib-concat": "^0.5.1",
    "grunt-contrib-copy": "^0.8.0",
    "grunt-contrib-cssmin": "^0.13.0",
    "grunt-contrib-jshint": "^0.11.2",
    "grunt-contrib-testem": "^0.5.16",
    "grunt-contrib-uglify": "^0.9.1",
    "grunt-githooks": "^0.3.1",
    "grunt-htmlhint": "^0.4.1",
    "grunt-ng-constant": "^1.1.0",
    "grunt-protractor-runner": "^2.1.0",
    "grunt-shell": "^1.1.2",
    "grunt-usemin": "^3.0.0",
    "http-server": "^0.6.1",
    "protractor": "^2.1.0",
    "protractor-http-mock": "^0.1.17",
    "shelljs": "^0.2.6",
    "sinon": "^1.15.4",
    "tmp": "0.0.23"
  },
  "scripts": {
    "postinstall": [
      "bower install",
      "grunt hookmeup"
    ],
    "preupdate-webdriver": "npm install",
    "update-webdriver": "webdriver-manager update",
    "preprotractor": "npm run update-webdriver",
    "protractor": "protractor test/protractor-conf.js",
    "update-index-async": "node -e \"require('shelljs/global'); sed('-i', /\\/\\/@@NG_LOADER_START@@[\\s\\S]*\\/\\/@@NG_LOADER_END@@/, '//@@NG_LOADER_START@@\\n' + cat('bower_components/angular-loader/angular-loader.min.js') + '\\n//@@NG_LOADER_END@@', 'app/index-async.html');\""
  }
}

protractor-http-mock not capturing communication with server

First, I'd like to say thanks to the devs of protractor-http-mock. Easy and intuitive:)

The app I am (helping) develop has a backend that I can run locally (and I usually do during development). The webapp will attempt to check authentication against the backend on initialization and will receive a 401 if it is not authenticated (in which case a modal will be displayed asking the user to log in).

If my backend is not running, everything seems to work like a charm. The auth-request is intercepted by protractor-http-mock and mock data is returned.

When my backend is running my first test as expected, but in proceeding tests, the login-modal appears. It looks like the response is actually passed through to the backend and the response is cached by angular (my guess). I'm having trouble debugging this in the browser but can post (some) code and can follow instructions if you need more info. The appearing modal is a potential issue as it can interfere with clicking if it overlaps any targets on the webpage.

Changing the mock folder structure.

maybe the mock folder could be in this format

mocks/example.com/path/path/GET/resource.js

then resource.js can just be the response and not need something like module.exports =

Match PUT/POST/PATCH data

I'd like to build some mock responses for good and bad PUT/POST/PATCH calls. Currently, I can match query params in my mocks with the request.params option, but it would be nice to have a similar option for request.data. Looks like the match method could call a matchData() function that would check whether config.data === expectationRequest.data.

Mock dynamic url's

Hi. I'm developing weather forecast app. Front-end of my app makes different get queries to server like this:
localhost:3000/api/weather/22.30935/113.921988888889/1447970462561
localhost:3000/api/weather/49.13234/22.679924588889/1447938462456

Is there any way to mock this dynamic routes by your module?

For example: can I use something like this:

mock([{
request: {
path: 'localhost:3000/api/weather///*',
method: 'GET'
},
response: {
status: 200,
data: {}
}
}]);
???

Thanks.

protractor config files & path joining

Hell Carlos,

Your changes to how to make protractor config file configurable is much better, than mine. Sorry, I was in a hurry for another task at hand, so I slashed a quick solution together.

It would be great if in any protractor config file we can do something like

{
rootDirectory: __dir,
protractorConfig: __filename
}

But we can't do that, because the following line in init-data which joins the directory with the filename

The line in https://github.com/atecarlos/protractor-http-mock/blob/master/lib/init-data.js#L35

executionConfig = require(path.join(moduleConfig.rootDirectory, moduleConfig.protractorConfig)).config

If I get a chance I can hack in a little logic to detect if protractorConfig is a full path and filename and not do path.join or not. Maybe over the weekend.

P.S. The console.log in http-mock are great for debugging 👍
P.S.S. In the readme file if you can promote protractor-http-mock is not relying on ngMockE2E that would be great. I think that's the key difference with other solutions, and ngMockE2E is very intrusive.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.