AngularJS testing with firebase/mockfirebase in a service - angularjs

I've been trying to write some unit tests for my services which use AngularFire to communicate with Firebase inside an Angular website.
I'm new to AngularJS and so I feel like I'm missing something obvious but couldn't find any great examples online (at least not that spoke to my limited knowledge).
I found some limited docs on MockFirebase https://github.com/katowulf/mockfirebase/tree/master/tutorials and that showed how to pretty much mock out the data so I did that.
For further examples of mockfirebase I looked at the angular fire's unit tests https://github.com/firebase/angularfire/tree/master/tests/unit but that didn't seem to show me the right way.
Here is my service --
app.service('Subscription', function ($firebase, FIREBASE_URL, $q) {
var ref;
var Subcription = {
ref: function () {
if (!ref) ref = new Firebase(FIREBASE_URL + "/subscriptions");
return ref;
},
validateSubscription: function(userId){
var defer = $q.defer();
$firebase(Subcription.ref().child(userId))
.$asObject()
.$loaded()
.then(function (subscription) {
defer.resolve(subscription.valid === true);
});
return defer.promise;
},
recordSubscription: function(userId){
return Subcription.ref().$set(userId, {valid: true});
}
};
return Subcription;
});
Here is the spec file --
describe('Service: subscription', function () {
// load the service's module
beforeEach(module('clientApp'));
// instantiate service
var subscription;
var scope;
beforeEach(inject(function (_Subscription_, $rootScope) {
MockFirebase.override();
subscription = _Subscription_;
scope = $rootScope.$new();
}));
it('allows access when the user id is in the subscription list', function () {
subscription.ref().push({'fakeUser': {valid: true}});
subscription.ref().flush();
var handler = jasmine.createSpy('success');
subscription.validateSubscription('fakeUser').then(handler);
scope.$digest();
expect(handler).toHaveBeenCalledWith(true);
});
});
It seems like the problem is that the promise never gets resolved inside of $asobject.$loaded because that angularfire part isn't happening.
I get the following as a result of the test: 'Expected spy success to have been called with [ true ] but it was never called.'

Related

Spying on service dependencies

I'm curious about the best way to spy on dependencies so I can make sure that their methods are being called in my services. I reduced my code to focus on the problem at hand. I'm able to test my service fine, but I want to also be able to confirm that my service (In this case metricService) has methods that are also being called. I know I have to use createSpyObj in some way, but while the function is executing properly, the spyObj methods are not being caught. Should I even be using createSpyObj? Or should I use spyObj? I'm a but confused about the concept of spying when it concerns dependencies.
UPDATE: When using SpyOn I can see one method getting called, but other methods are not
Test.spec
describe("Catalogs service", function() {
beforeEach(angular.mock.module("photonServicesCommons"));
var utilityService, metricsService, loggerService, catalogService, localStorageService;
var $httpBackend, $q, $scope;
beforeEach(
inject(function(
_catalogService_,
_metricsService_,
_$rootScope_,
_$httpBackend_
) {
catalogService = _catalogService_;
$scope = _$rootScope_.$new();
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', "/ctrl/catalog/all-apps").respond(
{
catalogs: catalogs2
}
);
metricsService = _metricsService_;
startScope = spyOn(metricsService, 'startScope')
emitSuccess = spyOn(metricsService, 'emitGetCatalogSuccess').and.callThrough();
endScope = spyOn(metricsService, 'endScope');
})
);
afterEach(function(){
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('get catalog', function(){
it("Should get catalogs", function(done) {
catalogService.normalizedDynamicAppList = testDynamicAppList1;
catalogService.response = null;
var promise3 = catalogService.getCatalog();
promise3.then(function (res) {
expect(res.catalogs).toEqual(catalogs2);
});
expect(metricsService.startScope).toHaveBeenCalled();
expect(metricsService.emitGetCatalogSuccess).toHaveBeenCalled();
expect(metricsService.endScope).toHaveBeenCalled();
$scope.$digest();
done();
$httpBackend.flush();
});
});
});
Service
public getCatalog(): IPromise<Interfaces.CatalogsResponse> {
if (this.response !== null) {
let finalResponse:any = angular.copy(this.response);
return this.$q.when(finalResponse);
}
return this.$q((resolve, reject) => {
this.metricsService.startScope(Constants.Photon.METRICS_GET_CATALOG_TIME);
this.$http.get(this.catalogEndpoint).then( (response) => {
let data: Interfaces.CatalogsResponse = response.data;
let catalogs = data.catalogs;
if (typeof(catalogs)) { // truthy check
catalogs.forEach((catalog: ICatalog) => {
catalog.applications.forEach((application: IPhotonApplication) => {
if( !application.appId ) {
application.appId = this.utilityService.generateUUID();
}
})
});
} else {
this.loggerService.error(this.TAG, "Got an empty catalog.");
}
this.response = data;
this.metricsService.emitGetCatalogSuccess();
console.log("CALLING END SCOPE");
this.metricsService.endScope(Constants.Photon.METRICS_GET_CATALOG_TIME);
resolve(finalResponse);
}).catch((data) => {
this.loggerService.error(this.TAG, "Error getting apps: " + data);
this.response = null;
this.metricsService.emitGetCatalogFailure();
reject(data);
});
});
} // end of getCatalog()
Instead of using createSpyObj, you can just use spyOn. As in:
beforeEach(
inject(function(
_catalogService_,
_$rootScope_,
_$httpBackend_,
_metricsService_ //get the dependecy from the injector & then spy on it's properties
) {
catalogService = _catalogService_;
metricsService = _metricsService_;
$scope = _$rootScope_.$new();
...
// create the spy object for easy referral later on
someMethodSpy = jasmine.spyOn(metricsService, "someMethodIWannaSpyOn")
})
);
describe('get catalog', function(){
it("Should get catalogs", function(done) {
catalogService.normalizedDynamicAppList = testDynamicAppList1;
catalogService.response = null;
var promise3 = catalogService.getCatalog();
...other expects
...
//do the spy-related expectations on the function spy object
$httpBackend.flush(); // this causes the $http.get() to "move forward"
// and execution moves into the .then callback of the request.
expect(someMethodSpy).toHaveBeenCalled();
});
});
I've used this pattern when testing complex angular apps, along with wrapping external imported/global dependencies in angular service wrappers to allow spying and mocking them for testing.
The reason that createSpyObject won't work here is that using it will create a completely new object called metricService with the spy props specified. It won't be the same "metricService" that is injected into the service being tested by the angular injector. You want to get the actual same singleton service object from the injector and then spy on it's properties.
The other source of dysfunction was the $httpBackend.flush()s location. The $httpBackend is a mock for the $http service: you pre-define any number of expected HTTP requests to be made by the code you are testing. Then, when you call the function that internally uses $http to make a request to some url, the $httpBackend instead intercepts the call to $http method (and can do things like validate the request payload and headers, and respond).
The $http call's then/error handlers are only called after the test code calls $httpBackend.flush(). This allows you to do any kind of setup necessary to prep some test state, and only then trigger the .then handler and continue execution of the async logic.
For me personally this same thing happens every single time I write tests with $httpBackend, and it always takes a while to figure out or remember :)

How can I Unit Test Rest Angular Service and Controller?

Rest-angular for Api Calling .
My Aim to Write a Unit test Case by calling Controller and Test all the Scope are assigned,the Code blocks of with REST API Response But not MOCK RESPONSE.
Rest Angular Service :-
(function () {
angular.module('movieApp').service('movieApiService', callMoviesApi);
function callMoviesApi(Restangular) {
this.getMyMovie= function (Id) {
return Restangular.one('movies/' + movieId).get().then(function(result){
return result.plain();
});
};
this.getMoviesList = function () {
return Restangular.all('movies').getList().then(function(result){
return result.plain();
});
};
}
}());
Where I am Injecting this Service to Controller as a Dependency
Controller Code Follows :-
angular.module('movieApp').controller('MoviesController', ['$scope','movieApiService',
function ($scope, MovieService) {
$scope.movie = $stateParams.movieId;
MovieService.getMovieDetails($scope.movie).then(function (result) {
$scope.movieDetails = result;
$scope.movieId = result._id;
$scope.movieName = result.displayName;
});
}
]);
I did tried to Write a Unit test for the Above Controller not Going good :-(
Test Code Follows:-
'use strict';
(function() {
describe('MoviesController', function() {
//Initialize global variables
var scope,stateParams={},
MoviesController;
// Load the main application module
beforeEach(module('movieApp'));
beforeEach(inject(function($controller, $rootScope,$stateParams) {
scope = $rootScope.$new();
stateParams.movieId='Baahubali';
HomeController = $controller('MoviesController', {
$scope: scope,
$stateParams:stateParams
});
}));
it('Should call movieApi and Assign Scopes', function() {
var Api="http://testsite.com/moives/thor";
var myScope=$httpBackend.expectGET(Api).passthrough();
expect(scope.movie).toBeDefined();
console.log('****'+scope.movie.displayName);
});
});
})();
Error is Raising :-
Error: Unexpected request: GET http://testsite.com/movies/undefined
Expected GET http://testsite.com/movies/undefined?
at $httpBackend (C:/wrokingdir2015/public/lib/angular-mocks/angular-mocks.js:1245)
at sendReq (C:/wrokingdir2015/public/lib/angular-mocks/public/lib/angular/angular.js:9695)
Could Any One help me to Write a Unit test case Which can Initialize controller and Assing Scopes like in real controller for testing .
Honestly iam New Guy for Unit testing .
I suggest Selenium with Cucumber for having the scenarios that you test in a nice and readable format
but for testing only a REST api you just need an implementation of javax.ws.rs.client.Client, I use org.glassfish.jersey.client.JerseyClient.
private final Client client = ClientBuilder.newClient();
e.g.
#When("^I want to retrieve all cells for the report with id \"([^\"]*)\".$")
public void accessCellReport(String id) {
response = client.target(URL).path(PathConstants.PATH_ID)
.resolveTemplate(PathConstants.PARAM_ID, reportId).request(MediaType.APPLICATION_JSON).get();
RestAssertions.assertResponseOk(response);
}
First of all i would use Restangulars one method as it supposed to be used.
Read more about it here: https://github.com/mgonto/restangular#creating-main-restangular-object
Restangular.one('movies', movieId);
In my service test i would do something like this to test that the correct endpoint has been called.
it('should call /movies/{movieId}', function() {
var spy = sinon.spy(Restangular, 'one');
var movieId = 1;
movieApiService.getMyMovie(movieId);
expect(spy).to.have.been.calledWith('movies', movieId);
});
Then I would make a sinon stub to mock the reponse from the service in another test for the controller.
it('should set my movies variabel after calling movie service', function() {
var mockResponse = [
{
id: 1,
title: 'Titanic'
},
{
id: 2,
title: 'American History X'
}
];
sinon.stub(movieApiService, 'getMyMovie')
.returns(
$q.when(
[
{
id: 1,
title: 'Titanic'
},
{
id: 2,
title: 'American History X'
}
]
);
);
expect($scope.movieList).to.equal(mockResponse);
});
And another test for checking that the controller catch function is called.
it('should call error handling if service rejects promise', function() {
sinon.stub(movieApiService, 'getMyMovie')
.returns(
$q.reject('an error occured when fetching movie');
);
});
I suggest using Selenium:
http://www.seleniumhq.org/.
Easy to write unit tests and can be automatized with jenkins build.

How to test a service function in Angular that returns an $http request

I have a simple service that makes an $http request
angular.module('rootApp')
.factory('projectService', ['$http', function ($http) {
return {
getProject: getProject,
getProjects: getProjects,
};
function getProject(id) {
return $http.get('/projects.json/', { 'params': { 'id': id }});
}
}]);
I'm wondering how can I test this simply and cleanly? Here's what I have so far in my test.
describe("Root App", function () {
var mockGetProjectResponse = null,
$httpBackend = null;
beforeEach(module('rootApp'));
beforeEach(inject(function (_$httpBackend_, projectService) {
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', '/projects.json/?id=1').response(mockGetProjectResponse);
}));
describe("should get projects successfully", inject(function (projectService) {
it("should return project", function () {
// I essentially want to do something like this (I know this isn't the right format).. but:
//expect(projectService.getProject(1)).toBe(mockGetProjectResponse);
});
}));
I want to avoid explicitly calling $http.get(...) in my test, and rather stick to calling the service function, i.e. projectService.getProject(1). What I'm stuck on is not being able to do something like this:
projectService.getProject(1)
.success(function (data) {
expect(data).toBe(whatever);
})
.error(function () {
});
Since there's 'no room' to call $httpBackend.flush();
Any help would be much appreciated. Thanks.
The usual recipe for testing promises (including $http) is
it("should return project", function () {
var resolve = jasmine.createSpy('resolve');
projectService.getProject(1).then(resolve);
expect(resolve).toHaveBeenCalledWith(mockGetProjectResponse);
});
A good alternative is jasmine-promise-matchers which eliminates the need for spy boilerplate code.
Here's a plunker that demonstrates both of them.
Generally the one may want to keep the methods that make $http calls as thin as possible and stub them instead, so mocking $httpBackend may not be required at all.
In current example the spec tests literally nothing and can be omitted and left to e2e tests if the code coverage isn't an end in itself.

chai-as-promised erroneously passes tests

I'm trying to write tests for a method that returns an angular promise ($q library).
I'm at a loss. I'm running tests using Karma, and I need to figure out how to confirm that the AccountSearchResult.validate() function returns a promise, confirm whether the promise was rejected or not, and inspect the object that is returned with the promise.
For example, the method being tested has the following (simplified):
.factory('AccountSearchResult', ['$q',
function($q) {
return {
validate: function(result) {
if (!result.accountFound) {
return $q.reject({
message: "That account or userID was not found"
});
}
else {
return $q.when(result);
}
}
};
}]);
I thought I could write a test like this:
it("it should return an object with a message property", function () {
promise = AccountSearchResult.validate({accountFound:false});
expect(promise).to.eventually.have.property("message"); // PASSES
});
That passes, but so does this (erroneously):
it("it should return an object with a message property", function () {
promise = AccountSearchResult.validate({accountFound:false});
expect(promise).to.eventually.have.property("I_DONT_EXIST"); // PASSES, should fail
});
I am trying to use the chai-as-promised 'eventually', but all my tests pass with false positives:
it("it should return an object", function () {
promise = AccountSearchResult.validate();
expect(promise).to.eventually.be.an('astronaut');
});
will pass. In looking at docs and SO questions, I have seen examples such as:
expect(promise).to.eventually.to.equal('something');
return promise.should.eventually.equal('something');
expect(promise).to.eventually.to.equal('something', "some message about expectation.");
expect(promise).to.eventually.to.equal('something').notify(done);
return assert.becomes(promise, "something", "message about assertion");
wrapping expectation in runs() block
wrapping expectation in setTimeout()
Using .should gives me Cannot read property 'eventually' of undefined. What am I missing?
#runTarm 's suggestions were both spot on, as it turns out. I believe that the root of the issue is that angular's $q library is tied up with angular's $digest cycle. So while calling $apply works, I believe that the reason it works is because $apply ends up calling $digest anyway. Typically I've thought of $apply() as a way to let angular know about something happening outside its world, and it didn't occur to me that in the context of testing, resolving a $q promise's .then()/.catch() might need to be pushed along before running the expectation, since $q is baked into angular directly. Alas.
I was able to get it working in 3 different ways, one with runs() blocks (and $digest/$apply), and 2 without runs() blocks (and $digest/$apply).
Providing an entire test is probably overkill, but in looking for the answer to this I found myself wishing people had posted how they injected / stubbed / setup services, and different expect syntaxes, so I'll post my entire test.
describe("AppAccountSearchService", function () {
var expect = chai.expect;
var $q,
authorization,
AccountSearchResult,
result,
promise,
authObj,
reasonObj,
$rootScope,
message;
beforeEach(module(
'authorization.services', // a dependency service I need to stub out
'app.account.search.services' // the service module I'm testing
));
beforeEach(inject(function (_$q_, _$rootScope_) {
$q = _$q_; // native angular service
$rootScope = _$rootScope_; // native angular service
}));
beforeEach(inject(function ($injector) {
// found in authorization.services
authObj = $injector.get('authObj');
authorization = $injector.get('authorization');
// found in app.account.search.services
AccountSearchResult = $injector.get('AccountSearchResult');
}));
// authObj set up
beforeEach(inject(function($injector) {
authObj.empAccess = false; // mocking out a specific value on this object
}));
// set up spies/stubs
beforeEach(function () {
sinon.stub(authorization, "isEmployeeAccount").returns(true);
});
describe("AccountSearchResult", function () {
describe("validate", function () {
describe("when the service says the account was not found", function() {
beforeEach(function () {
result = {
accountFound: false,
accountId: null
};
AccountSearchResult.validate(result)
.then(function() {
message = "PROMISE RESOLVED";
})
.catch(function(arg) {
message = "PROMISE REJECTED";
reasonObj = arg;
});
// USING APPLY... this was the 'magic' I needed
$rootScope.$apply();
});
it("should return an object", function () {
expect(reasonObj).to.be.an.object;
});
it("should have entered the 'catch' function", function () {
expect(message).to.equal("PROMISE REJECTED");
});
it("should return an object with a message property", function () {
expect(reasonObj).to.have.property("message");
});
// other tests...
});
describe("when the account ID was falsey", function() {
// example of using runs() blocks.
//Note that the first runs() content could be done in a beforeEach(), like above
it("should not have entered the 'then' function", function () {
// executes everything in this block first.
// $rootScope.apply() pushes promise resolution to the .then/.catch functions
runs(function() {
result = {
accountFound: true,
accountId: null
};
AccountSearchResult.validate(result)
.then(function() {
message = "PROMISE RESOLVED";
})
.catch(function(arg) {
reasonObj = arg;
message = "PROMISE REJECTED";
});
$rootScope.$apply();
});
// now that reasonObj has been populated in prior runs() bock, we can test it in this runs() block.
runs(function() {
expect(reasonObj).to.not.equal("PROMISE RESOLVED");
});
});
// more tests.....
});
describe("when the account is an employee account", function() {
describe("and the user does not have EmployeeAccess", function() {
beforeEach(function () {
result = {
accountFound: true,
accountId: "160515151"
};
AccountSearchResult.validate(result)
.then(function() {
message = "PROMISE RESOLVED";
})
.catch(function(arg) {
message = "PROMISE REJECTED";
reasonObj = arg;
});
// digest also works
$rootScope.$digest();
});
it("should return an object", function () {
expect(reasonObj).to.be.an.object;
});
// more tests ...
});
});
});
});
});
Now that I know the fix, it is obvious from reading the $q docs under the testing section, where it specifically says to call $rootScope.apply(). Since I was able to get it working with both $apply() and $digest(), I suspect that $digest is really what needs to be called, but in keeping with the docs, $apply() is probably 'best practice'.
Decent breakdown on $apply vs $digest.
Finally, the only mystery remaining to me is why the tests were passing by default. I know I was getting to the expectations (they were being run). So why would expect(promise).to.eventually.be.an('astronaut'); succeed? /shrug
Hope that helps. Thanks for the push in the right direction.

Angularjs and Jasmine: Testing a controller with a service making ajax call

I am very new to testing javascript. My application is using angularjs. I am using jasmine as a testing framework.
Here is the controller I am testing:
angular.module('logonController', ["ngval", "accountFactory"])
.controller("logonController", function logOnController(accountFactory, $scope, $window) {
$scope.hasServerError = false;
$scope.Logon = function () {
accountFactory.Logon($scope.data.LogOnModel)
.then(function (data) {
$window.location.href = "/";
},
function (data) {
$scope.hasServerError = true;
});
}
})
where accountFactory.Logon is making a Post request to the server.
What I want to test is when calling accountFactory.Logon:
On success - window.location.href is called
On error $scope.hasServerError is set to true
So far I have managed to do this:
"use strict";
describe("Logon Controller", function () {
var $scope, $location, $rootScope, $httpBackend, $controller, $window, createController;
beforeEach(function () {
module("logonController");
});
beforeEach(inject(function ($injector) {
$rootScope = $injector.get("$rootScope");
$scope = $rootScope.$new();
$location = $injector.get("$location");
$httpBackend = $injector.get("$httpBackend");
$controller = $injector.get("$controller");
$window = $injector.get("$window");
}));
beforeEach(function () {
createController = function () {
return $controller("logonController", {
"$scope": $scope,
});
};
$scope.data = {
LogOnModel: { username: "user", password: "pass" }
};
$window = { location: { href: jasmine.createSpy() } };
});
it("should redirect on successfull login", function () {
var controller = createController();
$httpBackend.whenPOST("/Account/Logon").respond(function (method, url, data, headers) {
return [200, {}, {}];
});
$scope.Logon();
$httpBackend.flush();
expect($window.location.href).toHaveBeenCalled();
});
});
My idea is to create a spy on $window.location.href and only check if it is called. But I am getting
Expected spy unknown to have been called.
As I said I am very new to testing javascript, so any help will be appreciated.
Sten Muchow's Answer is wrong in several aspects:
you can't specify a compound property name ("location.href") as 2nd parameter to spyOn. You have to give a real property name.
And even if you would do the spyOn correctly, andCallThrough() would still raise an exception, as $window.location.href is not a function which could be called through.
But he is still right in saying that you should not intermingle your controller test with the service test.
To answer the question:
The reason, that your expectation is not met (that even the spy still exists*) is, that you're doing the $window.location.href assignment inside a promise's then() function. That means, it will be executed asynchronously, namely AFTER your expect() call. To go around this, you would need to make your test work asynchronously (for how to do this I would like to advise you to the Jasmine documentation: http://jasmine.github.io/2.0/introduction.html).
* In accountFactory.Logon, by doing $window.location.href = (i.e. assignment) you will effectively overwrite your spy.
Even better solution:
Instead of manipulating $window.location.href, you should use $location.url().
$location is an Angular core service. You will benefit from the integration within the Angular application lifecycle (i.e. watchers will be automatically processed when the url changes) + it is seamlessly integrated with existing HTML5 APIs like History API: https://docs.angularjs.org/guide/$location
Then, you can spy on $location.url() as you would have spied on $window.location.href (if it had been a function).
You need to create a spy:
spyOn($window, 'location.href').andCallThrough();
But on a bigger note though, you shouldnt be testing the functionality of your service in the controller test.

Resources