Good afternoon to you all,
Just want to ask how to properly set the value of localStorage/cookie in unit testing. I have this code below where I set a cookie then tried to get the value of that cookie but it always disply null.
This snippet of code is the one that I'm trying to test:
scope.$on("$locationChangeSuccess", function(event, next, current)
{
if(location.path() === '/home') {
if(!Utils.isBlank(session.get('token'))) {
var usertype = session.get('usertype');
// console.log('landed home');
if(usertype === 'player') location.path('/user/dashboard');
if(usertype === 'broadcaster') location.path('/broadcaster/dashboard');
if(usertype === 'quizmaster') location.path('/quizmaster/dashboard');
}
}
});
My controllerSpec.js
describe('MainCtrl', function() {
var scope, api, security, clearAll, location, redirect, session, utility;
beforeEach(inject(function($rootScope, $controller ,$location, Api, Security, Utils, localStorageService){
scope = $rootScope.$new();
location = $location;
session = localStorageService
utility = Utils;
$controller("MainCtrl", {
$scope : scope,
localStorageService : session,
Api : Api,
$location : location,
Utility : utility
});
}));
it('should expect player to be redirected to /user/dashboard', function() {
//set the location to home
var home = spyOn(location, 'path');
var addSession = spyOn(session, 'add');
var token = 'testToken';
location.path('/home');
scope.$on('$locationChangeSuccess', {})
expect(home).toHaveBeenCalledWith('/home');
//mock a session
session.add('token',token);
expect(addSession).toHaveBeenCalled();
expect(session.get('token')).toEqual('testToken');
});
Error:
Chrome 24.0 (Linux) controllers MainCtrl MainCtrl should expect player to be redirected to /user/dashboard FAILED
Expected null to equal 'testToken'.
Even though I already set the token "session.add('token', token)" it still showing token is null. I added a spyOn to check if the session.add method was called and it did.
Please help.
You mocked the method add in the service. If you want to call it while spying it, you need to use andCallThrough()
var addSession = spyOn(session, 'add').andCallThrough();
This might not be obvious if you are new to Jasmine. There was an issue (couldn't find it, sorry) where people complained that this should be the default functionality of spyOn. IMHO, it's good the way it is, as you are supposed to only do Unit Tests, not expect your controller do a full integration test (i.e remove the session.get expect, you are not testing the session to work, that has to be in the library test).
Update Answering your comment, to test the url based on a token stored in local storage just do something like this:
spyOn(session, 'get').andReturn(token); //Remember, you are not testing the service, you assume it works.
Depending on what the value of token, you can do expect(location.path()).toBe('/registeredUserOnly')
Related
I am using the following code in a WebApi Controller:
public static T GetCookieValue<T>(this HttpRequestMessage request, string key)
{
var cookieCollection = request.Headers.GetCookies();
Trace.TraceInformation(JsonConvert.SerializeObject(cookieCollection));
foreach (var cook in cookieCollection.Select(c => c.Cookies.FirstOrDefault(x => x.Name == key)).Where(cook => cook != null))
{
return (T)Convert.ChangeType(cook.Value, typeof(T));
}
return default(T);
}
Usage
var myValue = this.Request.GetCookieValue<int>("activeTenant");
On the client side watching Fiddler I can see the value changing, however on the .NET side, it keeps giving me the same value over and over again. Is there some sort of Cookie or Header Cache that is causing this?
Here is some more information that I sorta figured out. My WebApi controller is inheriting from another and this call is in that base controller. If I do the call in the Sub-Classed controller it works fine. So now I am thinking this is Autofac handing me back cached requests??
Stumped on a couple failures and want to know if I'm understanding Mirage correctly:
1.In ember-cli-mirage, am I correct that the server response I define should reflect what my actual server is returning? For example:
this.get('/athletes', function(db, request) {
let athletes = db.athletes || [];
return {
athletes: athletes,
meta: { count: athletes.length }
}
});
I am using custom serializers and the above matches the format of my server response for a get request on this route, however, on two tests I'm getting two failures with this error: normalizeResponse must return a valid JSON API document: meta must be an object
2.Is mirage enforcing the json:api format, and is it doing so because of the way I'm setting up the tests?
For example, I have several tests that visit the above /athletes route, yet my failures occur when I use an async call like below. I would love to know the appropriate way to correctly overwrite the server response behavior, as well as why the normalizeResponse error appears in the console for 2 tests but only causes the one below to fail.
test('contact params not sent with request after clicking .showglobal', function(assert) {
assert.expect(2);
let done = assert.async();
server.createList('athlete', 10);
//perform a search, which shows all 10 athletes
visit('/athletes');
fillIn('.search-inner input', "c");
andThen(() => {
server.get('/athletes', (db, request) => {
assert.notOk(params.hasOwnProperty("contacts"));
done();
});
//get global athletes, which I thought would now be intercepted by the server.get call defined within the andThen block
click('button.showglobal');
});
});
Result:
✘ Error: Assertion Failed: normalizeResponse must return a valid JSON API document:
* meta must be an object
expected true
I tried changing my server response to a json:api format as suggested in the last example here but this looks nothing like my actual server response and causes my tests to fail since my app doesn't parse a payload with this structure. Any tips or advice must appreciated.
You are correct. Are the failures happening for the mock you've shown above? It looks to me like that would always return meta as an object, so verify the response is what you think it should be by looking in the console after the request is made.
If you'd like to see responses during a test, enter server.logging = true in your test:
test('I can view the photos', function() {
server.logging = true;
server.createList('photo', 10);
visit('/');
andThen(function() {
equal( find('img').length, 10 );
});
});
No, Mirage is agnostic about your particular backend, though it does come with some defaults. Again I would try enabling server.logging here to debug your tests.
Also, when writing asserts against the mock server, define the route handlers at the beginning of the test, as shown in the example from the docs.
I was able to get my second test to pass based on Sam's advice. My confusion was how to assert against the request params for a route that I have to visit and perform actions on. I was having to visit /athletes, click on different buttons, and each of these actions was sending separate requests (and params) to the /athletes route. That's is why I was trying to redefine the route handler within the andThen block (i.e. after I had already visited the route using the route definition in my mirage/config file).
Not in love with my solution, but the way I handled it was to move my assertion out of route handler and instead assign the value of the request to a top-level variable. That way, in my final andThen() block, I was able to assert against the last call to the /athletes route.
assert.expect(1);
//will get assigned the value of 'request' on each server call
let athletesRequest;
//override server response defined in mirage/config in order to
//capture and assert against request/response after user actions
server.get('athletes', (db, request) => {
let athletes = db.athletes || [];
athletesRequest = request;
return {
athletes: athletes,
meta: { count: athletes.length }
};
});
//sends request to /athletes
visit('/athletes');
andThen(() => {
//sends request to /athletes
fillIn('.search-inner input', "ab");
andThen(function() {
//sends (final) request to /athletes
click('button.search');
andThen(function() {
//asserts against /athletes request made on click('button.search') assert.notOk(athletesRequest.queryParams.hasOwnProperty("contact"));
});
});
});
I'm still getting console errors related to meta is not an object, but they are not preventing tests from passing. Using the server.logging = true allowed me to see that meta is indeed an object in all FakeServer responses.
Thanks again to Sam for the advice. server.logging = true and pauseTest() make acceptance tests a lot easier to troubleshoot.
I am pretty new at unit testing and AngularJS and I have some issue that I can't fix. One of my test is not working. I am trying to initiate a location.path() in my test by affecting a value, but in my controller, location.path() still have a undefined value.
Here is my controler:
angular.module('...')
.controller('SignUpCtrl', ['$location', function ($location) {
// Retrieve type of user
var userType = $location.path().substr(9);
if(userType == 'member'){
userType = 'user';
}
console.log($location.path());
console.log(userType);
$scope.uType = userType; ]);
And here is my test module:
describe('Controller: SignUpCtrl', function () {
// load the controller's module
beforeEach(module('...'));
var SignUpCtrl,
scope,
mockBackend,
environments,
location,
store;
beforeEach(inject(function ($controller, $rootScope, $httpBackend,$location,_Environments_) {
environments = _Environments_;
mockBackend = $httpBackend;
location = $location;
scope = $rootScope.$new();
SignUpCtrl = $controller('SignUpCtrl', {
$scope: scope,
$location: location
});
}));
it('should come from the right location', function(){
location.path('/sign-up/member');
expect(location.path()).toBe('/sign-up/member');
expect(scope.uType).toBe('user'); //Do not work
});
});
You're trying to use unit testing to do something that can only really be achieved using End-to-End (or E2E) testing. Unit testing in AngularJS is designed to test the javascript within a given module or sub-module (such as a service, factory, directive, etc). However, things like page navigation or browser location really need to be tested in an end-to-end testing environment.
Because of that, your $location object won't have all the normal methods (like path, url, etc). The $location object ends up simply being a "mock" of the actual $location object that you'd get in your module. So, you just need to move your test case for it('should come from the right location', function(){ ... }) to an end-to-end test and then continue on with your other module-specific unit tests. After you do that, you can simplify the $controller by only grabbing the $scope variable, as in the following:
scope = $rootScope.new();
SignUpCtrl = $controller('SignUpCtrl', {$scope: scope});
The guide for E2E testing can be found at this link. It walks you through how to write good E2E tests. There is a really great framework available for doing angular E2E tests called Protractor. The info for that is at this link. Protractor will soon (in 1.2) replace Karma as a better way to handle E2E testing.
As you know, inside unit tests it's built-in angularjs feature to mock XHR requests with $httpBackend - this is nice and helpful while writing unit tests.
Recently, I met with need of mocking XHR in case of file upload and discovered some problems.
Consider following code:
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", uploadProgress(event), false);
xhr.addEventListener("load", uploadComplete(event), false);
xhr.addEventListener("error", uploadError(event), false);
xhr.addEventListener("abort", uploadAbort(event), false);
xhr.open("POST", 'some url');
xhr.send(someData);
What I want to do is to do unit testing of such a code with mocking of XHR requests, but it's not possible do it because there is no $http service used here.
I tried this (and it's working and could be mocked with $httpBackend):
$http({
method: 'POST',
url: 'some url',
data: someData,
headers: {'Content-Type': undefined},
transformRequest: angular.identity})
.then(successCallback, errorCallback);
But in this case I don't know how to implement 'progress' callback and 'abort' callback (they are essential and required in case I am working on now).
I've seen information that latest Angular supports progress callback for promises (not sure though whether it's integrated with $http service), but what about abort callback?
Any ideas or maybe your met with something similar before?
If the $http service doesn't give you everything you need, you can still unit test the first block of code. First of all, change your code to use Angular's $window service. This is just a wrapper service, but it allows you to mock the object in your tests. So, you'll want to do this:
var xhr = new $window.XMLHttpRequest();
Then in your tests, just mock it and use spies.
$window.XMLHttpRequest= angular.noop;
addEventListenerSpy = jasmine.createSpy("addEventListener");
openSpy = jasmine.createSpy("open");
sendSpy = jasmine.createSpy("send");
xhrObj = {
upload:
{
addEventListener: addEventListenerSpy
},
addEventListener: addEventListenerSpy,
open: openSpy,
send: sendSpy
};
spyOn($window, "XMLHttpRequest").andReturn(xhrObj);
From there, you can make the different spies return whatever you want for the different tests.
You should mock $http and control any deferred, as you want more control over your test. Basically, mock $http provider and serve a custom implementation that exposes its deferred, then play with it.
You should not worry whether $http is working right or not, because it is supposed to, and is already tested. So you have to mock it and only worry testing your part of the code.
You should go something like this:
describe('Testing a Hello World controller', function() {
beforeEach(module(function($provide) {
$provide.provider('$http', function() {
this.$get = function($q) {
return function() {
var deferred = $q.defer(),
promise = deferred.promise;
promise.$$deferred = deferred;
return promise;
}
};
});
}));
it('should answer to fail callback', inject(function(yourService, $rootScope) {
var spyOk = jasmine.createSpy('okListener'),
spyAbort = jasmine.createSpy('abortListener'),
spyProgress = jasmine.createSpy('progressListener');
var promise = yourService.upload('a-file');
promise.then(spyOk, spyAbort, spyProgress);
promise.$$deferred.reject('something went wrong');
$rootScope.$apply();
expect(spyAbort).toHaveBeenCalledWith('something went wrong');
}));
});
And your service is simply:
app.service('yourService', function($http) {
return {
upload: function(file) {
// do something and
return $http({...});
}
};
});
Just note that promises notification is only available in the latest RC release. So, if you can't use it, just elaborate a little more the example and mock the XHR events and so.
Also note that you should preferably have one test case for each of the callbacks (fail, success and progress), in order to follow KISS principle.
Background:
I'm writing unit test for angular js controllers, which utilize angular $resources wrapped in services (for maintainability purposes).
Example controller:
name = 'app.controllers.UsersIndexCtrl'
angular.module(name, [])
.controller(name, [
'$scope'
'$location'
'$dialog'
'Users'
'UserRoles'
($scope, $location, $dialog, Users, UserRoles) ->
# Get users list
$scope.users = Users.query()
# rest...
])
Example resource service:
angular.module('app.services.Users', [])
.factory 'Users', ['$rootScope', '$http', '$resource', '$location' , ($rootScope, $http, $resource, $location)->
baseUrl = '/users'
Users = $resource baseUrl + '/:userId', {userId: '#_id'}
Users.getStatus = ->
console.log 'User::getStatus()'
req = $http.get baseUrl + '/status'
req.success (res)->
$rootScope.globalUserAccountSettings = res
unless $rootScope.$$phase then $rootScope.$apply()
# other, custom methods go here...
])
Most of unit test examples in angular suggest using $httpBackend and thus mocking the $http service in controllers. To be honest, I doubt if it's a good practice since if did so I'd have to hardcode request paths in all controller tests and I want to isolate unit behaviour. $httpBackend mock is really great but only if you are using $resource in controllers directly.
A typical single test using $httpBackend would look like:
it 'should be able to add a new empty user profile', ->
$httpBackend.flush()
l = $scope.users.length
$httpBackend.expect('POST', '/users').respond _.cloneDeep mockResponseOK[0]
$scope.add()
$httpBackend.flush()
expect($scope.users.length).toEqual l + 1
What if I created a mock User resource class instance, something like:
angular.module('app.services.Users', [])
.factory 'Users', ->
class Users
$save:(cb)->
$remove:->
#query:->
#get:->
Angular DI mechanisms will override old 'app.services.Users' module with this one in a transparent way and enable me to run checks with jasmine spies.
What bothers me is the fact that I wasn't able to find a single example supporting my idea. So the question is, which one would you use and why or what am I doing wrong?
I think it makes much more sense to stub this at a service level with Jasmine spies, as you suggested. You're unit testing the controller at this point, not the service -- the exact way in which an http request is made should not be a concern for this test.
You can do something in your spec like this:
var Users = jasmine.createSpyObj('UsersStub', ['query', 'get']);
beforeEach(inject(function($provide) {
$provide.factory('Users', function(){
return Users;
});
});
And then in your relevant tests, you can stub the individual service methods to return what you expect using methods like "andCallFake" on your spy object.
The best thing you can do is make a fake resource with the methods that were suppose to be called:
var queryResponse = ['mary', 'joseph'],
Users = function() {
this.query = function() {
return queryResponse;
},
scope, HomeCtrl;
};
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
HomeCtrl = $controller('HomeCtrl', {$scope: scope, Users: new Users()});
}));
it('has users in scope', function() {
expect(scope.users).toEqual(queryResponse);
});
I'm a newbie at this stuff. I've been writing my tests using coffeescript using a dsl, but I've ran into a similar problem today. The way I solved it was by creating a jasmine spy for my resource. Then I created a promise. When the promise is resolved, it will call the 'success' function that you pass in in the controller. Then in the 'it' method, I actually resolve the promise.
I think the code would look something like this using js and jasmine, but I didn't actually have time to check
beforeEach(inject(function($rootScope, $controller, $q ) {
scope = $rootScope.$new();
queryPromise = $q.defer()
User = jasmine.createSpyObject("UsersStub", ["query"]);
User.query.andCallFake(function(success,errror){queryPromise.promise.then(success,error)});
HomeCtrl = $controller('HomeCtrl', {$scope: scope, Users: new Users()});
}));
it('has users in scope', function() {
queryPrmomise.resolve({User1: {name"joe"})
expect(scope.users).toEqual(queryResponse);
});