I'm using Protractor in order to get some End-to-End testing in my application. In a test, I mock the backend-calls with a MockModule as following:
describe('Lets test a feature' , function(){
beforeAll(function () {
var customerE2E = function () {
angular.module('customerE2E', ['customer', 'ngMockE2E'])
.run(function ($httpBackend) {
$httpBackend.whenPOST('/api/data').respond(200);
$httpBackend.whenGET(/^.*/).passThrough();
});
};
browser.addMockModule('customerE2E', customerE2E);
});
it('correctly demonstrates my problem', function () {
expect(element(by.css('h4')).getText()).toMatch('Hello world');
}
})
This works really good but my problem is that I also want to test my application when the post responds with a 404, 500 etc. I have solved this by having a new "describe"-function for each case but it would be nice to just be able to override this call from inside the "it"-functions.
it('correctly demonstrates my problem', function () {
expect(element(by.css('h4')).getText()).toMatch('Hello world');
}
it('show error due to bad request', function () {
/*
Replace $httpBackend.whenPOST('/api/data').respond(200);
with $httpBackend.whenPOST('/api/data').respond(404);
*/
expect(element(by.css('h4')).getText()).toMatch('Failed api call');
}
I'm really new to this so I am wondering if there is a nice way to achieve an easy way to override the earlier MockModuled that was set in the beforeAll-function.
You can use beforeEach() to achieve it.
describe('Test Mock Module',function () {
var statusCodes = [200,404,500];
var currentTestNumber = 0;
beforeAll(function () {
var customerE2E = function () {
angular.module('customerE2E', ['customer', 'ngMockE2E'])
.run(function ($httpBackend) {
$httpBackend.whenPOST('/api/data').respond(200);
$httpBackend.whenGET(/^.*/).passThrough();
});
};
browser.addMockModule('customerE2E', customerE2E);
});
/*Below method will be executed before each test and set the required response respectively*/
beforeEach(function () {
$httpBackend.whenPOST('/api/data').respond(statusCodes[currentTestNumber++]);
});
it('test 200 status code',function () {
expect(element(by.css('h4')).getText()).toMatch('Message for 200 status code');
});
it('test 404 status code',function () {
expect(element(by.css('h4')).getText()).toMatch('Message for 404 status code');
});
it('test 500 status code',function () {
expect(element(by.css('h4')).getText()).toMatch('Message for 500 status code');
});
});
Related
I'm new testing with AngularJs and Jasmine. I'm trying to test a login POST $http service. I'm not pretty sure how to do it, but what I have I'm getting an error and I dont know why.
This is my login.service.js:
(function () {
'use strict';
angular
.module('app')
.service('loginService', loginService);
/** #ngInject */
function loginService($http) {
var url = 'http://localhost:8080/login';
var service = {
login: login
};
return service;
// ////////// //
function login(user) {
return $http.post(url, user);
}
}
})();
and this is my test:
describe('login component', function () {
var loginService;
var httpBackend;
var user = {
username: 'ADMIN',
password: 'ADMIN'
};
beforeEach(module('app'));
beforeEach(angular.mock.inject(function (_$httpBackend_, _loginService_) {
loginService = _loginService_;
httpBackend = _$httpBackend_;
}));
it('loginService should be defined', function () {
expect(loginService).toBeDefined();
});
it('loginService.login should be defined', function () {
expect(loginService.login).toBeDefined();
});
describe('We call the Login Service', function () {
beforeEach(function () {
spyOn(loginService, "login").and.callThrough();
});
it('we call the service', function () {
loginService.login(user);
});
it('we look if we called the login service', function () {
expect(loginService.login).toHaveBeenCalled();
});
it('loginService login we send the correct parameters', function () {
expect(loginService.login).toHaveBeenCalledWith('http://localhost:8080/login', 'POST', user);
});
});
});
I'm getting the next error when it runs:
PhantomJS 2.1.1 (Linux 0.0.0) login component We call the Login Service we look if we called the login service FAILED
Expected spy login to have been called.
.tmp/app/login/login.spec.js:37:50
loaded#http://localhost:9876/context.js:151:17
PhantomJS 2.1.1 (Linux 0.0.0) login component We call the Login Service loginService login we send the correct parameters FAILED
Error: <toHaveBeenCalledWith> : Expected a spy, but got Function.
Usage: expect(<spyObj>).toHaveBeenCalledWith(...arguments) in node_modules/jasmine-core/lib/jasmine-core/jasmine.js (line 3340)
.tmp/app/login/login.spec.js:41:54
loaded#http://localhost:9876/context.js:151:17
PhantomJS 2.1.1 (Linux 0.0.0): Executed 6 of 6 (2 FAILED) (0.041 secs / 0.046 secs)
Does anybody know what I'm doing wrong??
THANKS!!!!
You're testing it all wrong. Since you're testing the loginService and login is a method defined on that service, you shouldn't mock the method login.
You should only be mocking methods that don't belong to the code(a service in this case) that you're testing. So according to this, you should be mocking the post call that you perform on the $http service.
That can be done like this:
describe('login component', function () {
var loginService;
var $httpBackend;
var user = {
username: 'ADMIN',
password: 'ADMIN'
};
beforeEach(module('app'));
beforeEach(angular.mock.inject(function (_$httpBackend_, _loginService_) {
loginService = _loginService_;
$httpBackend = _$httpBackend_;
$httpBackend.whenPOST('http://localhost:8080/login', user).respond(201, 'SUCCESS');
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
/**
* Ideally, these should be inside a seperate describe block.
*/
// it('loginService should be defined', function () {
// expect(loginService).toBeDefined();
// });
// it('loginService.login should be defined', function () {
// expect(loginService.login).toBeDefined();
// });
//Like this.
describe('Initialization', function(){
it('loginService should be defined', function () {
expect(loginService).toBeDefined();
});
it('loginService.login should be defined', function () {
expect(loginService.login).toBeDefined();
});
});
describe('function: login', function () {
/*
* We should not be spying a method from the service we're trying to test.
* This is anti-pattern. We should be mocking the post call instead.
*/
// beforeEach(function () {
// spyOn(loginService, "login").and.callThrough();
// });
/**
* This test is not doing anything. Each it block should have atleast one expect.
*/
// it('we call the service', function () {
// loginService.login(user);
// });
/**
* This isn't what should be expected here. We should call the method and expect some response.
* The response will be mocked by us using $httpBackend's expectPOST method.
*/
// it('we look if we called the login service', function () {
// expect(loginService.login).toHaveBeenCalled();
// });
// it('loginService login we send the correct parameters', function () {
// expect(loginService.login).toHaveBeenCalledWith('http://localhost:8080/login', 'POST', user);
// });
it('should respond with status 201 and data \'SUCCESS\'', function(){
var response = loginService.login(user);
$httpBackend.flush();
response.success(function(res){
expect(res).toEqual("SUCCESS");
});
});
});
});
Now you would find that I've commented most of your code. And that's because the practice that has been followed is all wrong.
The practice that should be followed is, you should be using multiple describe blocks for different parts of you code. That's what I've done. Please do go through the comments that I've given. That would help you understand better.
Hope this helps!
Check this out Expected a spy, but got Function
I believe your error is that the method you're after with your spy is actually on the prototype. You'll see in the error trail the Expected a Spy but got a Function. Try spying on the prototype instead:
beforeEach(function () {
spyOn(loginService.prototype, "login").and.callThrough();
});
it('we look if we called the login service', function () {
expect(loginService.prototype.login).toHaveBeenCalled();
});
In your case, since you use $http service, you may assert with expectPOST() provided in ngMock, see more in angularjs docs, read the examples in the docs!
I am new to TDD and am trying to wire up a test, and have been stuck on it for hours. I keep getting the following error:
[$injector:modulerr] Failed to instantiate module AuthInterceptor due to:
Error: [$injector:nomod] Module 'AuthInterceptor' 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.8/$injector/nomod?p0=AuthInterceptor
at client/test/index.js:8237:13
at client/test/index.js:10251:18
at ensure (client/test/index.js:10175:39)
at module (client/test/index.js:10249:15)
at client/test/index.js:12786:23
at forEach (client/test/index.js:8490:21)
at loadModules (client/test/index.js:12770:6)
Here is my test:
import angular from 'angular';
import serviceModule from './auth.interceptor'
describe('wire.common.services', () => {
describe('AuthService', () => {
let AuthService;
beforeEach(angular.mock.module(serviceModule.name));
beforeEach(angular.mock.module(($provide) => {
$provide.factory('$q', () => ({}));
$provide.factory('$log', () => ({}));
}));
beforeEach(angular.mock.inject((_AuthService_) => {
AuthService = _AuthService_;
}));
it('should be a dummy test', () => {
expect(2).toEqual(2);
});
});
});
The actual code I'm testing:
export default function AuthInterceptor($q, $injector, $log) {
'ngInject';
return {
request(config) {
let AuthService = $injector.get('AuthService');
if (!config.bypassAuthorizationHeader) {
if (AuthService.jwtToken) {
config.headers.Authorization = `Bearer ${AuthService.jwtToken}`;
} else {
$log.warn('Missing JWT', config);
}
}
return config || $q.when(config);
},
responseError(rejection) {
let AuthService = $injector.get('AuthService');
if (rejection.status === 401) {
AuthService.backToDAS();
}
return $q.reject(rejection);
}
};
}
I don't understand why I'm getting this error - I provided all the dependencies for the service and am following what is outlined in the angular documentation. any help is appreciated!
Update, this is the code that I went with:
import angular from 'angular';
import AuthInterceptor from './auth.interceptor'
describe('Auth interceptor test', () => {
describe('AuthInterceptor test', () => {
let $httpBackend, $http, authInterceptor = AuthInterceptor();
beforeEach(angular.mock.module(($httpProvider, $provide) => {
$httpProvider.interceptors.push(AuthInterceptor);
$provide.factory('AuthService', () => ({
jwtToken: "hello",
backtoDAS: angular.noop
}));
}));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$http = $injector.get('$http');
}))
it('should have a request function', () => {
let config = {};
expect(authInterceptor.request).to.be.defined;
expect(authInterceptor.request).to.be.a('function');
})
it('the request function should set authorization headers', (done) => {
$httpBackend.when('GET', 'http://jsonplaceholder.typicode.com/todos')
.respond([{
id: 1,
title: 'Fake title',
userId: 1
}]);
$http.get('http://jsonplaceholder.typicode.com/todos').then(function(transformedResult) {
expect(transformedResult.config.headers.Authorization).to.be.defined;
expect(transformedResult.config.headers.Authorization).to.contain('Bearer')
done();
})
$httpBackend.flush();
});
it('should have a responseError function', () => {
expect(authInterceptor.responseError).to.be.defined;
expect(authInterceptor.responseError).to.be.a('function');
//TODO: test return value
// see that AuthService.backToDAS()
})
it('the error function should call backtoDAS', (done) => {
//the URL should be one that gives me a 401
$httpBackend.when('GET', 'https://wwws.mint.com/overview.event')
.respond([{
id: 1,
title: 'Fake title',
userId: 1
}]);
$http.get('https://wwws.mint.com/overview.event').then(function(transformedResult) {
console.log(transformedResult);
done();
}, function(error){
console.log(error);
done();
})
});
})
});
This means that AuthInterceptor Angular module wasn't defined (and by the way, relying on name is unsafe).
AuthInterceptor isn't a module but an injectable function. It can be tested in functional fashion as $http interceptor:
beforeEach(angular.mock.module(($httpProvider) => {
$httpProvider.interceptors.push(AuthInterceptor);
});
...
it('...', () => {
$httpBackend.when(...).respond(...);
$http.get(...).then((interceptedResult) => {
expect(interceptedResult)...
});
$rootScope.$digest();
});
or directly:
it('...', () => {
let interceptor = $injector.invoke(AuthInterceptor);
expect(interceptor).toEqual({
request: jasmine.any(Function),
requestError: jasmine.any(Function)
});
var config = { headers: {} };
interceptor.request(config);
expect(config)...
});
Services that produce side effects (AuthService, $log) should be stubbed.
This means that ng module is failing to load. :) And this happens while bootstrapping the app and ng module is first in a three element array: ng, ['$provide', function($provide) { ... }] and my own application module. It fails when loading the first one.
I've looked at console and I've copied this error message from it. There's no other error. None.
I hope you clicked that specific link and see that it doesn't give you any specific ideas about it. Unfortunately I've added this GitHub issue after exhausting other resources. I'm currently debugging angular code to get any further.
I'm attempted to unit test a service. I've injected the service however the method call getAllProducts() doesn't appear to run however the test still passes!
Plnkr
service.js
angular.module('vsApp')
.factory('productsDataService', function($http) {
var service = {
getAllProducts: getAllProducts
};
// get all products
function getAllProducts() {
return $http.get('/apiv1/getAllProducts/').then(function(data) {
return (data);
});
}
return service;
});
spec.js
// jasmine
describe('products data service', function () {
var $httpBackend, productsDataService;
beforeEach(module('vsApp'));
beforeEach(inject(function(_$httpBackend_, _productsDataService_) {
$httpBackend = _$httpBackend_;
productsDataService = _productsDataService_;
}));
it('should get all products', inject(function() {
console.info("get all");
// mock response for the http call in the service
$httpBackend.when('GET', '/apiv1/getAllProducts/')
.respond({name: 'item', price: '932'});
//this doesn't seem to run??
productsDataService.getAllProducts().then(function(response) {
expect(response.data.length).toBeGreaterThan(1);
});
}));
});
Ok, you have to make it sync. (all pending request will get resolved) using $http.flush();
Working demo as expected
productsDataService.getAllProducts().then(function(response) {
console.log(response);
expect(response.data.length).toBeGreaterThan(999);
});
$httpBackend.flush(); // <=============== here.
In my Controller I've defined the following service:
CrudService.getAllGroups().$promise.then(
function (response) { $scope.groups = response; },
function (error) { //error code.. }
);
Well, I want to test this service whether it gets a response or not. In test script at first I've defined a function to check whether the service is defined at all.
Test code:
describe('Ctrl: TestCtrl', function () {
beforeEach(module('testApp'));
var scope,
CrudService,
ctrl,
backend;
beforeEach(inject(function ($controller, $rootScope, _CrudService_, $httpBackend) {
scope = $rootScope.$new();
ctrl = $controller('TestCtrl', {
$scope: scope
});
CrudService = _CrudService_;
backend = $httpBackend;
}));
it('should defined the service getGroups', function () {
expect(CrudService.getGroups).toBeDefined();
});
//this is wrong!
it('should returns a successful response', function () {
backend.expectGET('http://localhost:63831/api/group').respond(200, 'success');
backend.flush();
});
});
I don't know how to get a response in the test. I'm new in unit testing and need some help.
For a better comprehension here is the service code:
//CrudService file:
...
return {
getAllGroups: function () {
return ResService.group.query();
}
}
...
//ResService file:
return {
group: $resource(baseUrl + '/api/group/:Id', {
Id: '#Id'
}, {})
}
Do anyone has an idea?
It's incorrect in the sense that it's not a unit test. If you are testing controller here, then you should mock CrudService and test that $scope.groups has been assigned correctly.
beforeEach(function () {
module(function ($provide) {
$provide.factory('CrudService', function () {
return {
getAllGroups: function () {
return {
$promise: null // return an actual promise here
}
}
}
});
});
});
it('should set groups', function () {
expect($scope.groups).toEqual('success')
});
And you need a separate spec to test if CrudService calling backend correctly.
I'm using CefSharp to run an AngularJS application. CefSharp has a method to RegisterJsObject, which assigns objects or properties onto the window before any of the client scripts execute.
I would love to be able to test this functionality with Protractor, but I'm not sure how I can access the window and assign objects before any of my tests run. Since my application depends on those objects being registered to the window I need a way to either hard-code this or mock it, without the objects registered, my application continues to throw undefined errors.
Is it even possible to access the window from Protractor? Something ideal would look something like:
describe('my test', function () {
beforeEach(function () {
window.foo = "bar";
});
it('should do stuff', function () {
browser.get('index.html');
// do stuff
});
});
EDIT: I have tried
describe('my test', function () {
it('should do stuff', function () {
browser.executeScript('return window.foo = { bar: {a: "b"}}').then(function () {
browser.get('#/');
browser.driver.manage().timeouts().implicitlyWait(10000);
});
});
But when the window actually loads I'm still seeing cannot read property 'bar' of undefined.
EDIT 2: Even tried executeAsyncScript with no luck.
describe('my test', function () {
it('should do stuff', function () {
browser.executeAsyncScript(function(cb) {
window.foo = { bar: { a: "b" } };
cb(true);
}).then(function (cb) {
browser.get('#/');
browser.driver.manage().timeouts().implicitlyWait(10000);
browser.wait(function () {
element(by.id('some-element')).isPresent();
});
expect(element(by.id('some-element')).isPresent()).toBe(true);
});
});
});
The problem is that the objects on window are being cleared when a new URL is loaded. You'll have to revert to using plain old webdriver for navigating to the URL:
describe('my test', function () {
it('should do stuff', function () {
browser.driver.get('youraddress');
browser.executeScript(function() {
window.foo = { bar: { a: "b" } };
});
browser.driver.manage().timeouts().implicitlyWait(10000);
browser.wait(function () {
element(by.id('some-element')).isPresent();
});
expect(element(by.id('some-element')).isPresent()).toBe(true);
});
This worked for me:
var originalDriverGet = browser.driver.get;
browser.driver.get = function() {
return originalDriverGet.apply(this, arguments).then(runScript);
};
function runScript() {
setTimeout(function() {
browser.executeScript("script to execute...");
});
}
Do this in protractor.conf.js in onPrepare block.