Created
April 7, 2013 01:44
-
-
Save emk/5328495 to your computer and use it in GitHub Desktop.
Testing Ember.js with Mocha, Sinon and Chai, including unit and integration tests. Runnable version available at: http://jsfiddle.net/ekidd/hCsws/13/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- Mocha test output goes here. --> | |
<div id="mocha"></div> | |
<!-- Handlebars templates for our application. --> | |
<script type="text/x-handlebars"> | |
<h1>Testing Ember.js with Mocha</h1> | |
{{outlet}} | |
</script> | |
<script type="text/x-handlebars" data-template-name="index"> | |
<p>{{#linkTo 'employees'}}Show employees{{/linkTo}}</p> | |
</script> | |
<script type="text/x-handlebars" data-template-name="employees"> | |
<h2>Employees</h2> | |
<ul> | |
{{#each employee in controller}} | |
<li>{{#linkTo 'employee' employee}}{{employee.name}}{{/linkTo}}</li> | |
{{/each}} | |
</ul> | |
</script> | |
<script type="text/x-handlebars" data-template-name="employee"> | |
<h2>{{name}}</h2> | |
<p>Salary: <span class="salary">${{salary}}</span></p> | |
<button {{action 'giveRaise'}}>Give Raise</button> | |
{{#if managedBy}} | |
<p class="managed-by"> | |
Managed by: {{#linkTo 'employee' managedBy}}{{managedBy.name}}{{/linkTo}} | |
</p> | |
{{/if}} | |
{{#if manages}} | |
<h3>Manages</h3> | |
<ul class="manages"> | |
{{#each employee in manages}} | |
<li>{{#linkTo 'employee' employee}}{{employee.name}}{{/linkTo}}</li> | |
{{/each}} | |
</ul> | |
{{/if}} | |
<p>{{#linkTo 'employees'}}All employees{{/linkTo}}</p> | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//================================================================================ | |
// Application Code | |
window.App = Ember.Application.create(); | |
// Our normal data store, which we be overridden when testing. | |
App.Store = DS.Store.extend({ | |
revision: 12, | |
adapter: DS.RESTAdapter.create() | |
}); | |
App.Employee = DS.Model.extend({ | |
name: DS.attr("string"), | |
salary: DS.attr("number"), | |
managedBy: DS.belongsTo("App.Employee"), | |
manages: DS.hasMany("App.Employee") | |
}); | |
App.Router.map(function () { | |
this.route("index", { path: "/" }); | |
this.route("employees", { path: "/employees" }); | |
this.route("employee", { path: "/employee/:employee_id" }); | |
}); | |
App.EmployeesRoute = Ember.Route.extend({ | |
model: function (params) { | |
return App.Employee.find(); | |
} | |
}); | |
App.EmployeeRoute = Ember.Route.extend({ | |
model: function (params) { | |
return App.Employee.find(params.employee_id); | |
} | |
}); | |
App.EmployeesController = Ember.ArrayController.extend(); | |
App.EmployeeController = Ember.ObjectController.extend({ | |
giveRaise: function () { | |
this.set("salary", this.get("salary") * 1.10); | |
} | |
}); | |
// Declare this explicitly so we can test it. | |
App.EmployeeView = Ember.View.extend({ | |
// Only needed so we can be called outside of a route by our unit tests. | |
templateName: 'employee' | |
}); | |
//================================================================================ | |
// Test Code | |
// Configure Mocha, telling both it and chai to use BDD-style tests. | |
mocha.setup("bdd"); | |
chai.should(); | |
// Replace our fixture-based store with a REST-based store for testing, so we | |
// don't need a server. We disable simulateRemoteResponse so that objects will | |
// appear to load at the end of every Ember.run block instead of waiting for a | |
// timer to fire. | |
App.Store = DS.Store.extend({ | |
revision: 12, | |
adapter: DS.FixtureAdapter.create({ simulateRemoteResponse: false }) | |
}); | |
// Declare some fixture objects to use in our test application. There's | |
// nothing like factory_girl or machinist yet. | |
App.Employee.FIXTURES = [{ | |
id: 1, | |
name: "Jane Q. Public", | |
salary: 80000, | |
managedBy: null, | |
manages: [2] | |
}, { | |
id: 2, | |
name: "John Q. Public", | |
salary: 60000, | |
managedBy: 1, | |
manages: [] | |
}]; | |
mocha.setup('bdd'); | |
// Run before each test case. | |
beforeEach(function () { | |
// Put the application into a known state, and destroy the defaultStore. | |
// Be careful about DS.Model instances stored in App; they'll be invalid | |
// after this. | |
// Currently broken, see: https://github.com/emberjs/data/issues/847 | |
//App.reset(); | |
// Display an error if asynchronous operations are queued outside of | |
// Ember.run. You need this if you want to stay sane. | |
Ember.testing = true; | |
}); | |
// Run after each test case. | |
afterEach(function () { | |
Ember.testing = false; | |
}); | |
// Load associations immediately, instead of waiting for FixtureAdapter's | |
// asynchronous loads. Basically, all we need to do is access each object | |
// from inside Ember.run. | |
// TODO: We can't test this or insert where needed until App.reset() works. | |
// TODO: Handle hasMany. | |
function loadAssociations(object /*, paths... */) { | |
var paths = Array.prototype.slice.call(arguments, 1); | |
for (var i = 0; i < paths.length; i++) { | |
var components = paths[i].split("."); | |
for (var j = 0; j < components.length; j++) { | |
Ember.run(function () { | |
var path = components.slice(0, j+1).join("."); | |
object.get(path); | |
}); | |
} | |
} | |
} | |
// Sample model test. | |
describe("App.Employee", function () { | |
it("has a name", function () { | |
var jane; | |
Ember.run(function () { | |
// Won't actually load until the end of the run-block. | |
jane = App.Employee.find(1); | |
}); | |
jane.get("name").should.equal("Jane Q. Public"); | |
}); | |
}); | |
// Sample controller test. | |
describe("App.EmployeeController", function () { | |
var model, controller; | |
beforeEach(function () { | |
Ember.run(function () { | |
model = App.Employee.createRecord({ salary: 100000 }); | |
controller = App.EmployeeController.create({ content: model }); | |
}); | |
}); | |
it("can give the employee a raise", function () { | |
var oldSalary = model.get("salary"); | |
Ember.run(function () { | |
controller.giveRaise(); | |
}); | |
model.get("salary").should.be(oldSalary * 1.1); | |
}); | |
}); | |
// Sample view test. | |
describe("App.EmployeeView", function () { | |
var controller, view; | |
beforeEach(function () { | |
Ember.run(function () { | |
var model = App.Employee.find(1); | |
controller = App.EmployeeController.create({ | |
// We need a container to test views with linkTo. | |
container: App.__container__, | |
content: model | |
}); | |
// If for some reason we want to isolate this, we can use | |
// a sinon stub to intercept certain calls. | |
sinon.stub(controller, "giveRaise"); | |
view = App.EmployeeView.create({ | |
controller: controller, | |
context: controller | |
}); | |
view.append(); // Hook up to our document. | |
}); | |
}); | |
afterEach(function () { | |
Ember.run(function () { | |
view.remove(); // Unhook from our document. | |
}); | |
}); | |
it("shows the employee's name", function () { | |
// This uses a chai-jquery assertion. | |
view.$("h2").should.have.text("Jane Q. Public"); | |
view.$(".manages li").text().should.match(/John/); | |
}); | |
it("has a button which gives the employee a raise", function () { | |
view.$("button").click(); | |
// We use a sinon-chai method here. | |
controller.giveRaise.should.have.been.calledOnce; | |
}); | |
}); | |
// Sample acceptance test. | |
describe("Employee features", function () { | |
it("give John's boss a raise", function () { | |
$("a:contains('Show employees')").click(); | |
$("a:contains('John')").click(); | |
$(".managed-by a").click(); | |
$(".salary").should.have.text("$80000"); | |
$("button:contains('Give Raise')").click(); | |
$(".salary").should.have.text("$88000"); | |
}); | |
}); | |
// Run all our test suites. Only necessary in the browser. | |
mocha.run(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Any chance that this will be updated to handle one to many or many to many relationships?