How to mock an error callback for a BackboneJS model (in destroy)? - backbone.js

Given the following BackboneJS 1.1.0 model / MarionetteJS 1.0.4 module:
MyApp.module('Product', function(Product, App, Backbone, Marionette, $, _) {
Product.Model = Backbone.Model.extend({
destroy: function() {
console.log("Product.destroy()");
return Backbone.Model.prototype.destroy.apply(this, arguments);
}
});
});
How would you simulate that the destroy function fails so you can test the associated behavior (such as a user notification alert message)? I use Jasmine 1.3.0 for testing in this project.

if you wanna test the error callback...you can mock the server response in jasmine by using http://sinonjs.org/
define your server in a before block:
var server, aProductInstance;
beforeEach(function() {
server = sinon.fakeServer.create();
aProductInstance = new Product.Model({id: 999});
});
restore it after each test:
afterEach(function() {
server.restore();
});
in your test, use respondWith method to return a non-200 response
server.respondWith(method, url, response);
like so
describe("fail to destroy", function() {
it("calls the error callback", function() {
server.respondWith("DELETE", "/products/destroy", [500, { "Content-Type": "application/json" }, '{ "error": "bad request" }']);
//call the method
aProductInstance.destroy();
//send the response
server.respond();
//now write your tests to see if error callback is called.
});
});

Related

Mock $http with configuration parameters

I'm applying some tests in an existing AngularJS application in order to ensure it's correct behaviour for future changes in the code.
I am pretty new with Jasmine & Karma testing, so I've decided to start with a small and basic service which performs an http request to the backend, and waits for the result with a promise, nothing new.
Here's the service method to test:
function getInformedConsent(queryParameters) {
var def = $q.defer(),
httpParameters = {
url: ENV.apiEndpoint + '/urlResource',
method: 'GET',
params: queryParameters,
paramSerializer: '$httpParamSerializerJQLike'
};
$http(httpParameters)
.then(
function (response) {
def.resolve(response);
},
function (error) {
def.reject(error);
}
);
return def.promise;
}
And here my test:
it('getInformedConsent method test', function() {
$httpBackend.expectGET(/.*\/urlResource?.*/g)
.respond(informedConsentJson.response);
var promise;
promise = InformedconsentService.getInformedConsent(informedConsentJson.queryParameters[0]);
promise
.then(function(response) {
console.log(response);
expect(response).toEqual(informedConsentJson.response);
});
$httpBackend.flush();
});
informedConsentJson as you can supose, is a fixture with input and the expected output.
Reading AngularJS documentation, I decided to use $httpBackend, because it's already a mock of $http service, so I thought it could be useful.
The problem is that somewhere in the code, someone is broadcasting a "$locationChangeStart" event and executing
$rootScope.$on('$locationChangeStart', function (event,current,old) {
/* some code here */
});
in app.js.
I'm not trying to change the URL, i'm just trying to get some data from the mocked backend.
I asume that is because I'm not using $http mock ($httpBackend) as it should be used.
Anyone can help me with $http with configuration JSON mock?
It's freaking me out.
Thank you all in advance for your time and your responses

Angular return promise from httpBackend.when()

How do you return a promise from httpBackend.when()? I wanted to load some canned data stored in a .json file and return that from httpBackend.whenGET(). When I try to return the promise from http.get('mydata.json') the response is returned to the failure callback of the factory.
function getAvailablePackagesComplete(response) {
return response.data;
}
function getAvailablePackagesFailed(error) { // {error = Object {data: undefined, status: 0, config: Object, statusText: ""}
$log.error(error.data.description);
return false;
}
function getAvailablePackages() {
return $http.get('/1.0/get-available-packages')
.then(getAvailablePackagesComplete)
.catch(getAvailablePackagesFailed)
}
var data = {"package": "test", "version": "1"}
$httpBackend.whenGET('/1.0/get-available-packages').respond(function(method, url, data) {
// return [200,data, {}] // this works
return $http.get('app/home/fixtures/mydata.json'); // contains {"package: "test", "version": "1"}
}); //this doesn't work
As it is currently, $httpBackend (from ngMockE2E) does not support promises within its .respond - See AngularJS GitHub Issue #11245. As $httpBackend should be used to avoid making real HTTP requests, but you could let some requests pass through.
From AngularJS Docs:
This implementation can be used to respond with static or dynamic responses via the when api and its shortcuts (whenGET, whenPOST, etc) and optionally pass through requests to the real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch templates from a webserver).
To work around what you're trying to do though, you could try to have getAvailablePackages() return the HTTP GET for your json file path and defining an $httpBackend.whenGET('pathTo.json').passThrough();
I was hitting the same issue and my use case was building a mock of my entire API in JS so that other people could work off line and develop the UI.
To achieve that I have developed a plugin called angular-mocks-async which decorates the httpBackend and adds the .whenAsync( ) APi to it. Than you can easily mock responses and return promises like so:
var app = ng.module( 'mockApp', [
'ngMockE2E',
'ngMockE2EAsync'
]);
app.run( [ '$httpBackend', '$q', function( $httpBackend, $q ) {
$httpBackend.whenAsync(
'GET',
new RegExp( 'http://api.example.com/user/.+$' )
).respond( function( method, url, data, config ) {
var re = /.*\/user\/(\w+)/;
var userId = parseInt(url.replace(re, '$1'), 10);
var response = $q.defer();
setTimeout( function() {
var data = {
userId: userId
};
response.resolve( [ 200, "mock response", data ] );
}, 1000 );
return response.promise;
});
}]);
You can return promises from http interceptors. Here is an example of delaying an HTTP call for 1 second. You can add this in your app.config(....)
$httpProvider.interceptors.push(function($q, $timeout) {
return {
'response': function(response) {
var defer = $q.defer();
$timeout(function() {
defer.resolve(response);
}, 1000);
return defer.promise;
}
};
});

Testing a database response in an Angularjs/Jasmine test

I'm attempting to unit test an angular factory in jasmine but I'm having trouble understanding how to get an actual response from my database.
I have the following factory code that retrieves an object containing company information based on a company ticker value.
The factory works fine but I'd like to test it in jasmine.
app.factory('adminOps',function($http, $log, $q){
return {
getByTicker: function(ticker){
return $http.post("http://localhost:3000/ticker", {"ticker": ticker})
.then(function(response){
if (typeof response.data === 'object') {
return response.data;
} else {
return $q.reject(response.data);
}
}, function(response) {
return $q.reject(response);
});
}
};
});
To test this I have the following jasmine code based on online examples I found.
describe('adminOps', function() {
var factory, http;
beforeEach(module('myApp'));
beforeEach(inject(function(_adminOps_, $httpBackend) {
factory = _adminOps_;
http = $httpBackend;
}));
it('Should retrieve company data', function(done) {
var testCompany = function(company) {
expect(company.name).toBe("Google Inc.");
};
var failTest = function(error) {
expect(error).toBeUndefined();
}
http.expectPOST('http://localhost:3000/ticker',{ticker:"GOOG"}).respond(200, {ticker:"GOOG"});
factory.getByTicker("GOOG")
.then(testCompany)
.catch(failTest)
.finally(done);
http.flush();
});
});
I get a Expected undefined to be 'Google Inc.'.
How can I call my factory and test for the correct name value for the ticker parameter I send?
UPDATE: Koen's code works correctly. I've found if you want to test values on a server, like a rest api, then you should try something like frisby, which is built on Jasmine.
Unittests should test your local code and should not have external dependencies. Therefor your test doesn't and shouldn't actually access your database.
$httpBackend allows you to mock the http request and response.
Your code mocks the $httpBackend as follows:
http.expectPOST('http://localhost:3000/ticker',{
ticker:"GOOG"
}).respond(200,{
ticker:"GOOG"
});
meaning it will respond with a response of
{ticker:"GOOG"}
So it doesn't have the 'name' attribute you need.
A proper way to test your 'getByTicker' method is with the following $httpBackend setup:
$http.expectPOST('http://localhost:3000/ticker', {
ticker: "GOOG"
}).respond(200, {
name: "Google Inc."
});

Angular test using $httpBackend fails with "400 thrown" error

For hours I've been trying to test my NewPostController with $httpBackend. The problem is whenever I set non-2xx status code in the response, the test fails.
NewPostController has the following method:
$scope.submit = function () {
var newPost = $scope.newPost;
PostService.insertPost(newPost).then(function (post) {
$location.path("/my-posts");
}, function (status) {
$scope.form.$setPristine();
$scope.error = status;
});
};
I have a problem testing the failure path:
it(...) {
...
$scope.post.text = "";
$httpBackend.expectPOST("/create-post", {"post":$scope.post}).respond(400);
$scope.submit();
$httpBackend.flush();
expect($scope.error).toBeDefined();
$scope.post.text = "This is a valid text.";
$httpBackend.expectPOST("/create-post", {"post": $scope.post}).respond(200);
$scope.submit();
$httpBackend.flush();
expect($location.path()).toBe("/my-posts");
});
The test fails with a message "400 thrown" (no callstack). I tried to change the order of subtests, use whenPOST instead of expectPOST and combine the methods as they do in Angular docs (https://docs.angularjs.org/api/ngMock/service/$httpBackend) but without success.
Please help.
EDIT:
Now when I look at PostService, it makes sense where the "400 thrown" comes from but I expected the error to be handled by angular. I threw it because of the section "Handling problems in nested service calls" of this article. It is supposed to be a shorter version of deferred.resolve/reject mechanism.
this.insertPost = function (newPost) {
return $http({
method: "post",
url: "/create-post",
data: {
post: newPost
}
}).then(function (res) {
return (res.data);
}, function (res) {
throw res.status;
});
};
This is indeed strange, and is perhaps something the angular team didn't consider.
When a promise is rejected by throwing (as you're doing), the angular $exceptionHandler service is called with the thrown exception. By default, this service just logs the exception in the browser console.
But when using ngMocks, this service is replaced by a mock implementation that can either log or rethrow the exception. The default mode is to rethrow, in order to make a test fail when an exception is thrown.
My advice would be to avoid using throw to simply reject a promise, and thus replace
function (res) {
throw res.status;
}
by
function (res) {
return $q.reject(res.status);
}
But if you really want to keep using throw, you can also configure the mock exceptionHandler to log instead of rethrowing:
beforeEach(module(function($exceptionHandlerProvider) {
$exceptionHandlerProvider.mode('log');
}));

BackboneJS testing collection fetch

I'm trying to test a Backbone collection's fetch. I'm using Sinon's fake server to set up a fake REST endpoint. The problem is that it seems like the request isn't sending.
I'm using Jasmine with Karma and running it through PhantomJS.
The problem is that the request is apparently not being sent. There aren't any errors but nothing is being logged to the console.
Here's the code:
describe("The Posts collection", function() {
var posts;
var server;
beforeEach(function() {
server = sinon.fakeServer.create();
posts = new PostCollection();
});
afterEach(function() {
server.restore();
});
it("should fetch the posts from the api", function() {
server.respondWith("GET", "/posts",
[200, { "Content-Type": "application/json" },
'{ "stuff": "is", "awesome": "in here" }']);
posts.fetch({
success: function(model, response, options) {
console.log("REQUEST SENT");
}
});
});
});
So as it turns out, I didn't read the docs carefully enough. With the fake server, you need to tell it to respond. I added the following after the call to posts.fetch():
server.respond();
It works perfectly now.

Resources