Backbone + Jasmine testing AJAX success callback in router - ruby-on-rails

Say I have the following Backbone Router:
class App.Routers.ThingsRouter extends Backbone.Router
initialize: -> new App.Collections.ThingsCollection()
index: ->
that = this
#collection.fetch success: ->
view = new App.Views.ThingsIndex(collection: that.collection)
$('#app-container').html(view.render().el)
I need to write a Jasmine spy that can watch this and ensure that App.Views.ThingsIndex() is called. However, as it's AJAX, the following won't work:
describe 'index', ->
#router = new App.Routers.ThingsRouter()
spyOn(#router.collection, 'fetch')
fake = { render: -> '' }
#previewsIndexStub = spyOn(Periscope.Views, 'PreviewsIndex').andReturn(fake)
#router.index()
expect(#previewsIndexStub).toHaveBeenCalled()
Because Jasmine runs the expectation function before the AJAX call can complete. Is there a good way to test a callback like this?

Use jasmines built in waitsFor & runs method in order to wait for the ajax call to complete before executing your expect function. See Jasmine - Asynchronous specs for documentation on these two functions.
describe 'index', ->
#router = new App.Routers.ThingsRouter()
spyOn(#router.collection, 'fetch')
fake = {}; fake.render = -> '';
#previewsIndexStub = spyOn(Periscope.Views, 'PreviewsIndex').andReturn(fake)
#router.index()
waitsFor => #previewsIndexStub.wasCalled
###
Or if you want to wait for the method to be called more than once
use form waitsFor => #previewsIndexStub.callCount > 1
###
runs => expect(#previewsIndexStub).toHaveBeenCalled()

Related

How can I return values with an RSpec class_double and as_stubbed_const?

I have a class MyVoucherClass that calls a separate service within a Rails application.
In the class that I am testing, VoucherIssuer, I am calling two of MyVoucherClass’s class methods, issue_voucher and activate_voucher which POST to the separate service.
I want to stub the entire MyVoucherClass and what values its class methods return. From RSpec documentation and further searching I have found that the following should work:
subject(:issue_credits) { described_class.new.issue_credits }
let(:my_voucher_class_double) do
class_double(MyVoucherClass,
issue_voucher: { voucher_id: "ABC123" }.to_json,
activate_voucher: instance_double(VoucherClass, voucher_id: "ABC123")
).as_stubbed_const
end
context “when using MyVoucherClass” do
it “calls on MyVoucherService” do
issue_credits
end
end
However, it throws the error:
WebMock::NetConnectNotAllowedError: Real HTTP connections are disabled. Unregistered request: POST [separate service url]
which means that the method return value stubbing is not working.
I am working around this with multiple allow(MyVoucherClass) ... and_return() statements instead, but I am wondering why the class double and as_stubbed_const are not working, because it would be ideal to do it in one class_double instead of allow twice.
let & let!
Note that let is lazy-evaluated: it is not evaluated until the first time
the method it defines is invoked. You can use let! to force the method's
invocation before each example.
Either you can call my_voucher_class_double inside it block to invoked or use let! instead of let
Using let
subject(:issue_credits) { described_class.new.issue_credits }
let(:my_voucher_class_double) do
class_double(MyVoucherClass,
issue_voucher: { voucher_id: "ABC123" }.to_json,
activate_voucher: instance_double(VoucherClass, voucher_id: "ABC123")
).as_stubbed_const
end
context “when using MyVoucherClass” do
it “calls on MyVoucherService” do
my_voucher_class_double
issue_credits
end
end
Using let!
subject(:issue_credits) { described_class.new.issue_credits }
let!(:my_voucher_class_double) do
class_double(MyVoucherClass,
issue_voucher: { voucher_id: "ABC123" }.to_json,
activate_voucher: instance_double(VoucherClass, voucher_id: "ABC123")
).as_stubbed_const
end
context “when using MyVoucherClass” do
it “calls on MyVoucherService” do
issue_credits
end
end

How to test angular directive that use templateUrl with angular-rails-templates

I'm using teaspoon as the test runner, which load all template files (using angular-rails-templates), which populate the templates module with stuff like this:
angular.module("templates").run(["$templateCache", function($templateCache) {
$templateCache.put("directives/sample/tmpl.html", '<span>Hey</span>')
}]);
And my directive coffee is like this:
angular.module 'myApp'
.directive 'sample', () ->
restrict: 'E'
templateUrl: 'directives/sample/tmpl.html'
scope:
address: "="
controllerAs: 'vm'
bindToController: true
controller: () ->
And my test coffee
describe "sample directive", ->
element = undefined
beforeEach ->
module("myApp", "templates")
# Store references to $rootScope and $compile
# so they are available to all tests in this describe block
beforeEach ->
inject ($compile, $rootScope) ->
scope = $rootScope.$new()
scope.address = "abcdef"
element = $compile("<div><sample address='address'/></div>")(scope)
scope.$digest()
it "Replaces the element with the appropriate content", ->
expect(element.html()).toContain "Hey"
The directive will never render with this. If I replace the templateUrl with template content then it runs fine. But that is not useful at all. What am I missing to let the directive render during test with templateUrl?
You haven't closed the <sample> tag in $compile
Try changing:
element = $compile("<div><sample address='address'</div>")(scope)
To
element = $compile("<div><sample address='address'></sample></div>")(scope)

Angular Rails Karma testing Controller with factory (with backend api calls) dependency

I have a projectFactory:
#app.factory "projectFactory", ['$http', ($http) ->
factory = {}
factory.loadProject = (projectId) ->
$http.get( endpoint(projectId) )
(endpoint is a method that generates the backend api url)
I then have a projectCtrl that is dependent on that factory:
#app.controller 'ProjectCtrl', ['$scope','$routeParams', 'projectFactory', ($scope, $routeParams, projectFactory) ->
$scope.projectId = $routeParams.projectId
$scope.loadProject = (projectId) ->
projectFactory.loadProject(projectId)
.success((data)->$scope.project = data.project)
I then have my project_control_spec test:
'use strict'
describe "ProjectCtrl", ->
beforeEach module 'app'
ProjectCtrl = {}
$scope = {}
projectFactory = {}
beforeEach ->
module($provide) ->
$provide.factory "projectFactory", projectFactory
module inject($controller, $rootScope) ->
$scope = $rootScope.$new()
ProjectCtrl = $controller 'ProjectCtrl', {
$scope : $scope,
$routeParams: {projectId: 1},
}
it "should instantiate a PC", ->
expect(ProjectCtrl).toBeDefined()
it "should have access to the projectId via the routeParams", ->
expect($scope.projectId).toEqual(1)
it "should have access to projectFactory", ->
expect($scope.projectFactory).toBeDefined()
it "should create $scope.project when calling loadProject", ->
expect($scope.project).toBeUndefined();
expect($scope.loadProject(1)).toBe(1)
expect($scope.project).toEqual({//a project object})
I am getting the error ReferenceError: Can't find variable: $provide, when trying to require my projectFactory
You cannot inject $provide on line module inject($controller, $rootScope, $provide) ->. It is also not used or needed in any case.
You should also test this case with $httpBackend. Check the first example.
What if you try this ?
(It is in javascript, sorry I can't write coffee yet)
beforeEach( inject (function( $injector ){
$provide = $injector.get('$provide');
// ...
}));
I'm not familiar with CoffeeScript, but this is how I would do it in plain old JS:
var projectFactory = {};
beforeEach(function () {
module('app', function ($provide) {
$provide.factory('projectFactory', projectFactory);
});
});
When taking some of your code and running it through a Coffee to JS interpreter, I get the following result:
describe("ProjectCtrl", function() {
var $scope, ProjectCtrl, projectFactory;
beforeEach(module('app'));
ProjectCtrl = {};
$scope = {};
projectFactory = {};
beforeEach(function() {
module($provide)(function() {
$provide.factory("projectFactory", projectFactory);
});
});
});
Basically you're trying to load a second module called $provide, when what you actually want to do is open up a config block for the first module (app) and inject $provide into the configuration block.
If you were using angular.module for your actual implementation of app, it'd look something like this:
angular.module('app', []).config(function ($provide) {
$provide.value('..', {});
$provide.constant('...', {});
/** and so on **/
});
Whereas in your specs when using angular-mocks, a config block gets set like this:
module('app', function ($provide) {
$provide.value('..', {});
/** and so on **/
});
I hope that helps. I'm not sure how you would actually write this in CoffeeScript, but I'm sure you can figure that one out. When taking the result of the Coffee to JS interpreter of your code, I received the same result - there is no variable $provide.

AngularJS: Retrieve newly created Record Id in CoffeeScript

To create a new record added from a form in AngularJS, I have:
task = Task.save($scope.newTask)
But I need to also retrieve the new id that was created upon save. The answer found here: Not getting updated values after call to AngularJS $save seems to address what I need, but when I convert to CoffeeScript:
task = $scope.newTask
task.$save (msg, headers) ->
console.log task.id
It does not work. I am using Ruby on Rails. Any help is appreciated.
Edit:
I am getting the error on the code above: "Object # has no method 'save'"
Here is a bigger picture of my code:
app = angular.module("appName", ["ngResource"])
app.factory "Task", ["$resource", ($resource) ->
$resource("/projects/:project_id/tasks/:id", {project_id: "#project_id", id: "#id"}, {update: {method: "PUT"}, destroy: { method: 'DELETE' }})
]
#TaskCtrl = ["$scope", "Task", ($scope, Task) ->
$scope.tasks = Task.query()
$scope.saveTask = ->
task = $scope.newTask
task.$save (msg, headers) ->
console.log task.id
$scope.tasks.push(task)
$scope.newTask = {}
]
You are trying to save newTask as it was an instance of the Task resource, but it is not, you are creating it as an empty object ($scope.newTask = {}).
You should create it as $scope.newTask = new Task()
Or save is as you seem to suggest in the first line of code: Task.save($scope.newTask).
The result of the Task.save() call will depend on what the server returns, it's JSON data, if you return the new ID in the JSON response of the POST request, then you'll get it in your data, if you just return an empty "created" response, with a Location header, you shuld subsequently retrieve the data. If you return nothing at all, that's the point in which you touch ruby, and return something useful from the server.

Changing state of to do list tasks with Backbone in Rails app

I'm very new to Backbone and I'm making a to do list app to learn the basics. Right now you can add a task and it is rendered in the to do list. Each task has the properties name (string) and complete(boolean). I'd like to make it so that when the checkbox (.toggle) is checked, the 'complete' property is changed to true. I also have an 'x' button (.destroy), that when clicked should remove the task from the database. I'm having trouble getting the markComplete and clear events to work correctly. Here's my tasks_index.js.coffee view:
class Backbonetodo.Views.TasksIndex extends Backbone.View
template: JST['tasks/index']
events:
'submit #new_task': 'createTask'
'click .toggle': 'markComplete'
'click .destroy': 'clear'
#I'd like this to change complete to true and put a line through the list item
markComplete: ->
#collection.set(complete:true)
initialize: ->
#collection.on('reset', #render, this)
#collection.on('add', #appendTask, this)
render: ->
$(#el).html(#template())
#collection.each(#appendTask)
this
appendTask: (task) ->
view = new Backbonetodo.Views.Task(model: task)
$('#tasks').append(view.render().el)
createTask: (task) ->
event.preventDefault()
attributes = name: $('#new_task_name').val()
#collection.create attributes,
wait: true
success: -> $('#new_task')[0].reset()
error: #handleError
handleError: (task, response) ->
if response.status == 422
errors = $.parseJSON(response.responseText).errors
for attribute, messages of errors
alert "#{attribute} #{message}" for message in messages
#This should remove the selected task from the database
clear: (task) ->
event.preventDefault()
#collection.remove()
This might also help. Here's my task.js.coffee view:
class Backbonetodo.Views.Task extends Backbone.View
template: JST['tasks/task']
tagName: 'li'
render: ->
$(#el).html(#template(task: #model))
this
markComplete: (event) ->
# You need to get somehow specific task id
# Example: id = $(event.currentTarget).data('id'), if you have the id in the DOM
#collection.get(id).set(complete:true)
# You have here a parameter task which is actually event
clear: (task) ->
event.preventDefault() # This is task :p
# Same as before: get your task or task id
# Example:
# taskId = $(event.currentTarget).data('id')
# task = #collection.get(taskId)
#collection.remove(task)
My examples depends on your template.
Suggestion: try to use #$ instead of $, to scope elements to your view's element, by the way I used $

Resources