Accessing $http data in Protractor / E2E tests (AngularJS) - javascript

I have a bunch of Unit tests that are going well, and I've started to add Protractor E2E tests to my project. I'm doing okay testing interactive elements on the page, but I'm having trouble testing for certain data being sent out of the browser.
For instance, I want to see if clicking a certain button produces a POST to a certain endpoint.
I have protractor set up using the following:
/*globals global*/
module.exports = function() {
'use strict';
var chai = require('chai')
, promised = require('chai-as-promised');
global.expect = chai.expect;
chai.use(promised);
}();
I understand how to use Protractor to interact:
it('send data to the "log" endpoint when clicked', function() {
var repeater = element.all(by.repeater('finding in data.items'));
repeater.get(0).click().then(function() {
// $http expectation
});
});
However, I don't know how to set up $httpBackend in Protractor so I can capture the data that gets sent as a result of the .click() event. Do I need an additional module?
In Karma/Mocha I would simply:
beforeEach(module('exampleApp'));
describe('logging service', function() {
var $httpPostSpy, LoggingService;
beforeEach(inject(function(_logging_, $http, $httpBackend) {
$httpPostSpy = sinon.spy($http, 'post');
LoggingService = _logging_;
backend = $httpBackend;
backend.when('POST', '/api/log').respond(200);
}));
it('should send data to $http.post', function() [
LoggingService.sendLog({ message: 'logged!'});
backend.flush();
expect($httpPostSpy.args[0][1]).to.have.property('message');
});
});
But I don't know how to get a reference to $httpBackend and inject modules in Protractor.

End to end testing is about testing the code is manner that is similar to how an end user will do. So verifying whether a remote request is made should be validated against a visible outcome, such as data getting loaded into a div or grid.
Still if you want to validate remote requests are made, you can create a mock back end setup using the ngMockE2E module, which contains a mock $htpBackend similar to the one in ngMock.
Look at the documentation on the $httpBackend https://docs.angularjs.org/api/ngMockE2E/service/$httpBackend

$httpBackend is for mocking a fake call to the server. In e2e test you normally do want to actually make a call to the server. It's important to note that most element locators in protractor return promises.
That means with this code your test will know to wait until the response from the server comes back and then assert that the text is the p tag is the correct data from the server.
my-file.spec.js
'use strict';
describe('The main view', function () {
var page;
beforeEach(function () {
browser.get('/index.html');
page = require('./main.po');
});
it('should have resultText defined', function () {
expect(page.resultText).toBeDefined()
})
it('should display some text', function() {
expect(page.resultText.getText()
.then())
.toEqual("data-that-should-be-returned");
});
});
my-file.po.js
'use strict';
var MainPage = function() {
this.resultText = element(by.css('.result-text'));
};
module.exports = new MainPage();

Related

Unit testing angular service with dependencies

I have the following Jasmine unit test:
describe('myService', function () {
var myService, $q;
// Instantiate the app
beforeEach(module('myApp'));
beforeEach(inject(function (_myService_, fileSystemService, $q) {
myService = _myService_;
spyOn(fileSystemService, 'listFiles').and.callFake(function () {
var deferred = $q.defer();
deferred.resolve('mockresult');
return deferred.promise;
});
}));
it('checks the number of outbound files', inject(function ($rootScope) {
var result;
myService.sendOutboundFiles2().then(function (res) {
result = res;
});
$rootScope.$digest();
expect(result).toBe('mockresult');
}));
});
Which tests this very simple service function:
sendOutboundFiles2() {
return fileSystemService.listFiles('Cached/Outbound').then(function(outfiles) {
return outfiles;
})
}
However when the test runs, it fails with a spurious Error: Unexpected request: GET blah\blah\blah.html No more request expected at $httpBackend error but i have no idea why as neither this test nor the service dependencies do anything with $httpBackend.
MORE INFO
If i comment out my existing controller tests, I get this error:
If i add my controller tests back in, I get this error:
So depending on which tests i add or remove, the HTML file in the GET error changes. But all the controller tests run fine. WTF?!?!?!!??!?!!?
The problem is caused by Ionic's keen prefetching of all templates into a cache. No idea why this doesn't occur when testing a controller though. The problem only appears when i was testing a service. Any way, I found this thread: Karma test breaks after using ui-router and the relevant fix is to add this snippets before injecting any dependencies:
beforeEach(module(function($provide) {
$provide.value('$ionicTemplateCache', function(){} );
}));
This stubs out the $ionicTemplateCache and prevents it from trying to preload all ui-router templates into the Ionic cache.

How to stub a method that is called from an outer scope to the function under test?

I have a Redis client that is created thus using the node_redis library (https://github.com/NodeRedis/node_redis):
var client = require('redis').createClient(6379, 'localhost');
I have a method I want to test whose purpose is to set and publish a value to Redis, so I want to test to ensure the set and publish methods are called or not called according to my expectations. The tricky thing is I want this test to work without needing to fire up an instance of a Redis server, so I can't just create the client because it will throw errors if it cannot detect Redis. Therefore, I need to stub the createClient() method.
Example method:
// require('redis').createClient(port, ip) is called once and the 'client' object is used globally in my module.
module.exports.updateRedis = function (key, oldVal, newVal) {
if (oldVal != newVal) {
client.set(key, newVal);
client.publish(key + "/notify", newVal);
}
};
I've tried several ways of testing whether set and publish are called with the expected key and value, but have been unsuccessful. If I try to spy on the methods, I can tell my methods are getting called by running the debugger, but calledOnce is not getting flagged as true for me. If I stub the createClient method to return a fake client, such as:
{
set: function () { return 'OK'; },
publish: function () { return 1; }
}
The method under test doesn't appear to be using the fake client.
Right now, my test looks like this:
var key, newVal, oldVal, client, redis;
before(function () {
key = 'key';
newVal = 'value';
oldVal = 'different-value';
client = {
set: function () { return 'OK'; },
publish: function () { return 1; }
}
redis = require('redis');
sinon.stub(redis, 'createClient').returns(client);
sinon.spy(client, 'set');
sinon.spy(client, 'publish');
});
after(function () {
redis.createClient.restore();
});
it('sets and publishes the new value in Redis', function (done) {
myModule.updateRedis(key, oldVal, newVal);
expect(client.set.calledOnce).to.equal(true);
expect(client.publish.calledOnce).to.equal(true);
done();
});
The above code gives me an Assertion error (I'm using Chai)
AssertionError: expected false to equal true
I also get this error in the console logs, which indicates the client isn't getting stubbed out when the method actually runs.
Error connecting to redis [Error: Ready check failed: Redis connection gone from end event.]
UPDATE
I've since tried stubbing out the createClient method (using the before function so that it runs before my tests) in the outer-most describe block of my test suite with the same result - it appears it doesn't return the fake client when the test actually runs my function.
I've also tried putting my spies in the before of the top-level describe to no avail.
I noticed that when I kill my Redis server, I get connection error messages from Redis, even though this is the only test (at the moment) that touches any code that uses the Redis client. I am aware that this is because I create the client when this NodeJS server starts and Mocha will create an instance of the server app when it executes the tests. I'm supposing right now that the reason this isn't getting stubbed properly is because it's more than just a require, but the createClient() function is being called at app startup, not when I call my function which is under test. I feel there still ought to be a way to stub this dependency, even though it's global and the function being stubbed gets called before my test function.
Other potentially helpful information: I'm using the Gulp task runner - but I don't see how this should affect how the tests run.
I ended up using fakeredis(https://github.com/hdachev/fakeredis) to stub out the Redis client BEFORE creating the app in my test suite like so:
var redis = require('fakeredis'),
konfig = require('konfig'),
redisClient = redis.createClient(konfig.redis.port, konfig.redis.host);
sinon.stub(require('redis'), 'createClient').returns(redisClient);
var app = require('../../app.js'),
//... and so on
And then I was able to use sinon.spy in the normal way:
describe('some case I want to test' function () {
before(function () {
//...
sinon.spy(redisClient, 'set');
});
after(function () {
redisClient.set.restore();
});
it('should behave some way', function () {
expect(redisClient.set.called).to.equal(true);
});
});
It's also possible to mock and stub things on the client, which I found better than using the redisErrorClient they provide for testing Redis error handling in the callbacks.
It's quite apparent that I had to resort to a mocking library for Redis to do this because Sinon couldn't stub out the redisClient() method as long as it was being called in an outer scope to the function under test. It makes sense, but it's an annoying restriction.

Angular testing http request error

I am trying to test a http request with dynamic url
I have something in my service file like
My service file.
//other service codes..
//other service codes..
var id = $cookies.id;
return $http.get('/api/product/' + id + '/description')
//id is dynamic
Test file
describe('test', function () {
beforeEach(module('myApp'));
var $httpBackend, testCtrl, scope;
beforeEach(inject(function (_$controller_, _$httpBackend_, _$rootScope_) {
scope = _$rootScope_.$new();
$httpBackend = _$httpBackend_;
testCtrl = _$controller_('testCtrl', {
$scope: scope
});
}));
it('should check the request', function() {
$httpBackend.expectGET('/api/product/12345/description').respond({name:'test'});
$httpBackend.flush();
expect(scope.product).toBeDefined();
})
});
I am getting an error saying
Error: Unexpected request: GET /api/product/description
I am not sure how to test the dynamic url. Can anyone help me about it? Thanks a lot!
You don't have id set in your code, so the url becomes:
/api/product//description
Which is reduced to what you see in the unexpected request (// -> /)
So, why isn't id defined? Show the code where you set it.
In testing 'dynamic' urls, you need to set up your test so that you know what the value of id is, and expect that. There isn't a way to expect patterns of urls.
You can modify the value of cookies by changing the first line of your describe block:
beforeEach(module('myApp', function($provide) {
$provide.value('$cookies', {
id: 3
});
}));
Now you can expect that id will be three when the URL call happens.
This is pretty crude though. You could also just inject $cookies in the second beforeEach block and set it
beforeEach(inject(function($cookies) {
$cookies.put('id', 3);
}))

Spyon provider during config phase in angular.js application

I am writing unit tests for an Angular.js application (with karma and jasmine), and I want to test a certain behavior in the CONFIG phase of a module. I would like to confirm that a certain function of a PROVIDER is being called. I thought I could do this with a spy on the provider's method, but gaining access to the provider before the "expect" has proven rather tricky.
Here is some example code:
Module Code (being tested)
var myApp = angular.module('myApp', ['restangular']);
myApp.config(['RestangularProvider', function (RestangularProvider) {
RestangularProvider.setBaseUrl('http://someurl:someport/');
}]);
I've tried various solutions to get a reference to the RestangularProvider and apply a spy to it, and all failed. The closest I was able to get was the code below:
Unit Test Code
describe("Test if setBaseUrl was called", function () {
var RestangularProvider;
beforeEach(module('myApp', function(_RestangularProvider_) {
RestangularProvider = _RestangularProvider_;
spyOn(RestangularProvider, "setBaseUrl").and.callThrough();
}));
it("should call setBaseUrl.", function() {
expect(RestangularProvider.setBaseUrl).toHaveBeenCalled();
});
});
I do actually get the reference to the RestangularProvider, but the "config" function of the module gets called before that, so I think the spy doesn't get set-up.
I did find a post where the author solved a similar situation with a "work around" by testing the configured "service" instead of testing the actual call to the provider's method. In the example above, I would test the Restangular.configuration.baseUrl in my expect instead of testing the actual call to the provider's setBaseUrl method, but this seemed like it would not be adequate in certain situations.
I am rather new to Angular.js so this may simply be a case of being totally clueless as to the whole "testing config phase", so if that's the case, please feel free to set me straight :]
Any suggestions, critiques or pointers?
I finally solved the problem by separating out the module, whose provider I wanted to spy on, into a diferent "beforeEach" block. The altered code is below, but I still would appreciate any comments as to the whole idea of whether or not this is actually an "adequate test".
describe("Test if setBaseUrl was called", function () {
var RestangularProvider;
//Setup the spy.
beforeEach(function () {
module("restangular", function(_RestangularProvider_) {
RestangularProvider = _RestangularProvider_;
spyOn(_RestangularProvider_, 'setBaseUrl').and.callThrough();
});
});
beforeEach(module('myApp'));
it("should call setBaseUrl.", function() {
expect(RestangularProvider.setBaseUrl).toHaveBeenCalled();
});
});
As described by OP above, you do need to get the provider before calling the module you want to test.
However, there's no need to separate it in two beforeEach blocks. You also must call inject() function (even if you have nothing to inject) at the end of the beforeEach block.
describe('Test if setBaseUrl was called', function () {
var RestangularProvider;
//Setup the spy.
beforeEach(function () {
module('restangular', function(_RestangularProvider_) {
RestangularProvider = _RestangularProvider_;
spyOn(_RestangularProvider_, 'setBaseUrl').and.callThrough();
});
module('myApp');
inject();
});
it('should call setBaseUrl.', function() {
expect(RestangularProvider.setBaseUrl).toHaveBeenCalled();
});
});
Source: http://java.dzone.com/articles/unit-testing-config-and-run

Unit test when loading things at app run with AngularJS

I need my app to run some configuration at runtime vi an HTTP endpoint.
I wrote a simple service to do that:
module.factory('config', function ($http, analytics) {
return {
load: function () {
$http.get('/config').then(function (response) {
analytics.setAccount(response.googleAnalyticsAccount);
});
}
}
});
Next, I call this module in a run block of my app module:
angular.module('app').***.run(function(config) {
config.load();
});
All is working well when the app is running but in my unit tests, I get this error: "Error: Unexpected request: GET /config"
I know what it means but I don't know how to mock it when it happens from a run block.
Thanks for your help
EDIT to add spec
Calling this before each
beforeEach(angular.mock.module('app'));
Tried this to mock $httpBackend:
beforeEach(inject(function($httpBackend) {
$httpBackend.expectGET('/config').respond(200, {'googleAnalyticsAccount':});
angular.mock.module('app')
$httpBackend.flush();
}));
But got:
TypeError: Cannot read property 'stack' of null
at workFn (/Users/arnaud/workspace/unishared-dredit/test/lib/angular/angular-mocks.js:1756:55)
TypeError: Cannot read property 'stack' of null
at workFn (/Users/arnaud/workspace/unishared-dredit/test/lib/angular/angular-mocks.js:1756:55)
TypeError: Cannot read property 'stack' of null
at workFn (/Users/arnaud/workspace/unishared-dredit/test/lib/angular/angular-mocks.js:1756:55)
EDIT since update to AngularJS 1.0.6
Since I've updated to AngularJS 1.0.6, advised by Igor from the Angular team, the issue is gone but now I've now got this one, which sounds more "normal" but I still can't figure out how to make it works.
Error: Injector already created, can not register a module!
I struggled with this error for a little while, but managed to come up with an sensible solution.
What I wanted to achieve is to successfully stub the Service and force a response, on controllers it was possible to use $httpBackend with a request stub or exception before initiating the controller.
In app.run() when you load the module it initialises the object and it's connected Services etc.
I managed to stub the Service using the following example.
describe('Testing App Run', function () {
beforeEach(module('plunker', function ($provide) {
return $provide.decorator('config', function () {
return {
load: function () {
return {};
}
};
});
}));
var $rootScope;
beforeEach(inject(function (_$rootScope_) {
return $rootScope = _$rootScope_;
}));
it("defines a value I previously could not test", function () {
return expect($rootScope.value).toEqual('testing');
});
});
I hope this helps your app.run() testing in the future.
I don't know if you are still looking for an answer to this question. But here is some information that might help.
$injector is a singleton for an application and not for a module. However, angular.injector will actually try to create a new injector for each module (I suppose you have a
beforeEach(module("app"));
at the beginning.
I had the same problem while using Angular, RequireJS, Karma and Jasmine and I figured out two ways to solve it. I created a provider for the injector function as a separate dependency in my tests. For example MyInjectorProvider which provides a singleton instance of $injector.
The other way was to move the following statements:
beforeEach(module("app"));
beforeEach(inject(function($injector){
...
})
inside the test suite description. So here is how it looked before:
define(['services/SignupFormValidator'], function(validator){
var validator;
beforeEach(module("app"));
beforeEach(inject(function($injector){
validator = $injector.get("SignupFormValidator");
})
describe("Signup Validation Tests", function(){
it("...", function(){...});
});
});
After applying the fix it looks like this:
define(['services/SignupFormValidator'], function(validator){
var validator;
describe("Signup Validation Tests", function(){
beforeEach(module("app"));
beforeEach(inject(function($injector){
validator = $injector.get("SignupFormValidator");
});
it("...", function(){...});
});
});
Both the solutions worked in my case.
You should mock every HTTP request with ngMock.$httpBackend. Also, here is a guide.
Update
You don't need the angular.mock.module thing, just need to inject your app module. Something like this:
var httpBackend;
beforeEach(module('app'));
beforeEach(inject(function($httpBackend) {
httpBackend = $httpBackend;
$httpBackend.expectGET('/config').respond(200, {'googleAnalyticsAccount': 'something'});
}));
In your tests, when you need the mocked http to answer, you will call httpBackend.flush(). This is why we have a reference to it, so you don't need to inject it in every single test you have.
Note you will need to load angular-mock.js in order to it work.

Categories

Resources