Sinonjs: how to mock Backbone fetch - backbone.js

I use Backbone and have the following function in accountsView.js:
loadData: function () {
this.accountsCollection.fetch()
.done(_.bind(this.loadDefaultAccounts, this))
.fail(_.bind(this._accountsLoadFailed, this));
},
In qunit test I'm trying to mock it like this:
sandbox.stub(Backbone.Collection.prototype, "fetch").yieldsTo("done", {});
But get the following error while running test:
"fetch expected to yield to 'done', but no object with such a property
was passed."
What I missed?

yieldsTo looks to me like it's meant to deal with callback based code.
To mock AJAX requests, you should setup a fake server and do something like
this.server.respondWith("GET", "/some/article/comments.json",
[200, { "Content-Type": "application/json" },
'[{ "id": 12, "comment": "Hey there" }]']);

So thanks for hints. In order to my test works the function in the view should like this:
loadData: function () {
this.accountsCollection.fetch({
success: _.bind(this.loadDefaultAccounts, this),
error: _.bind(this._accountsLoadFailed, this),
});
},
Or using in tests fake server as #TJ suggested.

Related

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.

How to mock an error callback for a BackboneJS model (in destroy)?

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.
});
});

Module factory is not returning data

I am trying to read JSON reply from server. You can find my code here.
https://github.com/ameyjah/feeder
In firefox firebug, I can see that server has returned JSON reply but when I store that into $scope.variable, I am not able to access that information.
Module code
var res
angular.module('myApp.services', ['ngResource'])
.factory('feedFetcher', ['$resource',
function($resource) {
var actions = {
'sites': {method:'GET', params: { action:"sites"} ,isArray:false},
'feeds': {method:'GET', params: { action:"sites"} ,isArray:false}
}
res = $resource('/api/:action', {}, actions);
return res
}
]);
Controller code
$scope.sites = feedFetcher.sites().sites;
console.log($scope.sites);
Reply seen in firebug:
{
"sites": [
{
"id": 0,
"title": "google"
},
{
"id": 1,
"title": "yahoo"
}
]
}
I think I have messed up the way I should define my factory but I am not able to identify. Any help would be helpful.
When you call sites() it returns an empty object and initiates an AJAX request, which will populate the "sites" property on that object. I think you should use it like this:
$scope.results = feedFetcher.sites();
console.log($scope.results.sites); // will be undefined as AJAX not complete
Then in your html you can use the results and they will be filled in when AJAX completes, or watch for it:
$scope.$watch('results.sites', function() {
// this will be called twice, once initially
// and again whenever it is changed, aka when AJAX completes
console.log($scope.results.sites);
};

ExtJS call directFn, failed

In the ExtJS 3, I want to invoke a method, like below. Looks like method in server side is not invoked. I can't use 'directFn' this way ? how to fix it ?
The server side is C#.
Thanks
function showDetail(recordId) {
Ext.Ajax.request({
directFn: Report.showDetail,
success: received,
failure: function () { alert('failure'); },
params: { recordId: recordId }
});
}
function received(response) {
var x = Ext.decode(response.responseText);
alert(x);
}
Uh, yeah, you can't just create a parameter and expect it to mean something to Ext. You'll need to call a url and pass directFn as part of the url or as a parameter, depending on how your server-side stuff is set up.

Resources