Presentation by Anthony Panozzo / @panozzaj
ntimes
#!/bin/bash
successes=0
failures=0
for ((n=0; n < $1; n++)); do
# run all arguments except for the count as new command
"${@:2}"
if [[ $? == 0 ]]; then
successes=$[successes + 1]
else
failures=$[failures + 1]
say -v Zarvox -r400 "failure"
fi
echo -e "\nSuccesses: $successes"
echo -e "Failures: $failures\n"
done
# usage:
# $ ntimes grunt test:mocha --grep 'test I want to run'
Takeaways:
var test = require('tape').test;
test('equivalence', function(t) {
t.equal(1, 1, 'these two numbers are equal');
t.end();
});
test('async', function (t) {
t.plan(2);
t.equal(2 + 3, 5);
setTimeout(function() {
t.equal(5 + 5, 10);
}, 500);
});
describe("A Jasmine suite", function() {
var foo = 0;
beforeEach(function() {
foo += 1;
});
it("is just a function, so it can contain any code", function() {
expect(foo).toEqual(1);
});
it("can be pending");
it("can have more than one expectation, and async too!", function(done) {
expect(foo).toEqual(2);
setTimeout(function() {
expect(true).toEqual(true);
done();
}, 500);
});
});
var assert = chai.assert;
assert.typeOf(foo, 'string');
assert.equal(foo, 'bar');
assert.lengthOf(foo, 3)
assert.property(tea, 'flavors');
assert.lengthOf(tea.flavors, 3);
chai.should(); // var should = require('chai').should();
foo.should.be.a('string');
foo.should.equal('bar', 'it is not right for some reason');
foo.should.have.length(3);
tea.should.have.property('flavors').with.length(3);
var expect = chai.expect;
expect(foo).to.be.a('string');
expect(foo).to.equal('bar', 'it is not right for some reason');
expect(foo).to.have.length(3);
expect(tea).to.have.property('flavors').with.length(3);
// talk about undefined
// talk about jshint
suite('Array', function() {
setup(function() {
// ...
});
suite('#indexOf()', function() {
test('should return -1 when not present', function() {
assert.equal(-1, [1, 2, 3].indexOf(4));
});
});
});
describe('Array', function() {
beforeEach(function() {
// ...
});
describe('#indexOf()', function() {
context('when not present', function() {
it('should not throw an error', function() {
(function() {
[1,2,3].indexOf(4);
}).should.not.throw();
});
it('should return -1', function() {
[1,2,3].indexOf(4).should.equal(-1);
});
});
});
});
var Factory = require('factory-lady'),
User = require('../../app/models/user'),
Post = require('../../app/models/post');
var emailCounter = 1;
Factory.define('User', User, {
email: function(cb) { cb('user' + emailCounter++ + '@example.com'); },
state: 'activated',
password: '123456',
});
Factory.define('Post', Post, {
user: Factory.assoc('User', 'id'),
subject: 'Hello World',
content: 'Lorem ipsum dolor sit amet...',
});
Factory.build('Post', function(post) {
// post is a Post instance that is not saved
});
Factory.build('Post', {
user: myUser,
title: 'Foo'
}, function(post) {
// build a post and override user and title
});
Factory.create('Post', function(post) {
// post is a saved Post instance
});
'use strict';
var _ = require('lodash'),
BBPromise = require('bluebird'),
mongoose = BBPromise.promisifyAll(require('mongoose'));
// Clear collections before each test to avoid pollution.
beforeEach(function() {
// Dropping the whole collection will drop indexes,
// which is not what we want.
return BBPromise.map(_.keys(mongoose.models), function(modelName) {
return mongoose.model(modelName).removeAsync({});
});
});
it("should stub method differently based on arguments", function() {
var callback = sinon.stub();
callback.withArgs(42).returns(1);
callback.withArgs(1).throws("TypeError");
callback(); // No return value, no exception
callback(42); // Returns 1
callback(1); // Throws TypeError
});
// given this function:
function throttle(callback) {
var timer;
return function() {
clearTimeout(timer);
var args = [].slice.call(arguments);
timer = setTimeout(function() {
callback.apply(this, args);
}, 1000);
};
}
var clock;
before(function() { clock = sinon.useFakeTimers(); });
after(function() { clock.restore(); });
it("calls callback after 100ms", function () {
var callback = sinon.spy();
throttle(callback)();
clock.tick(999);
assert(callback.notCalled);
clock.tick(1);
assert(callback.calledOnce);
// Also:
// assert.equals(new Date().getTime(), 1000);
}
// With rewire you can change these variables
var fs = require("fs"),
path = "/somewhere/on/the/disk";
function readSomethingFromFileSystem(cb) {
fs.readFile(path, "utf8", function(err, data) {
if (err) {
cb('error reading file!');
} else {
cb('file contents: ' + data);
}
});
}
exports.readSomethingFromFileSystem = readSomethingFromFileSystem;
var rewire = require("rewire");
var myModule = rewire("../lib/myModule.js");
describe('readSomethingFromFileSystem', function() {
context('normal operation', function() {
beforeEach(function(done) {
var fsStub = {
readFile: function(path, encoding, cb) {
expect(path).to.equal("/somewhere/on/the/disk");
cb(null, "stubbed file contents");
}
};
myModule.__set__("fs", fsStub);
done();
});
it('should respond with the file contents', function(done) {
myModule.readSomethingFromFileSystem(function(result) {
expect(data).to.equal('file contents: stubbed file contents');
done();
});
});
});
});
...
context('error case', function() {
beforeEach(function(done) {
var fsStub = {
readFile: function(path, encoding, cb) {
expect(path).to.equal("/somewhere/on/the/disk");
cb(new Error(''));
}
};
myModule.__set__("fs", fsStub);
done();
});
it('should respond with the file contents', function(done) {
myModule.readSomethingFromFileSystem(function(result) {
expect(data).to.equal('error reading file!');
done();
});
});
});
...
var nock = require('nock');
var weatherStub = nock('http://weatherapi.com')
.get('/london')
.reply(200, 'rainy, 50˚F');
'use strict';
var _ = require('lodash');
var Factory = require('factory-lady');
var BBPromise = require('bluebird');
var request = BBPromise.promisifyAll(require('superagent'));
var config = require('../../../config/config');
var categories, user;
describe('Categories controller', function() {
describe('list', function() {
beforeEach(function createUser(done) {
Factory.create('User').then(function(_user) {
user = _user;
done();
});
});
beforeEach(function createCategories(done) {
Factory.createList('Category', 3, [
{ name: 'Cheeses' },
{ name: 'Apples', },
{ name: 'Bananas', },
], function(_categories) {
categories = _categories;
done();
});
});
it('should respond with a sorted list of the categories',
request.get(config.baseUrl + '/api/v1/categories')
.set('Authorization', 'Bearer ' + user.createLoginToken())
.endAsync().then(function(response) {
response.status.should.equal(200);
response.headers['content-range'].should.eql('1-1/3');
response.body.should.eql([
'Apples',
'Bananas',
'Cheeses',
]);
done();
});
});
});
});
someModule.controller('MyController', [
'$scope',
'dep1',
'dep2',
'moment',
'q',
'_',
'$omg',
'$wtf', function(
$scope, dep1, dep2,
moment, q, _, $omg, $wtf) {
...
userService
.getSubredditsSubmittedToBy("yoitsnate")
.then(function(subreddits) {
$scope.subreddits = subreddits;
});
angular.module("reddit").service("userService",
function($http) {
return {
getSubredditsSubmittedToBy: function(user) {
return $http.get("http://api.reddit.com/user/" + user + "/submitted.json")
.then(function(response) {
var posts, subreddits;
posts = response.data.data.children;
// transform data to be only subreddit strings
subreddits = posts.map(function(post) {
return post.data.subreddit;
});
// de-dupe
subreddits = subreddits.filter(function(element, position) {
return subreddits.indexOf(element) === position;
});
return subreddits;
});
}
};
});
"use strict";
describe("reddit api service", function() {
var redditService, httpBackend;
beforeEach(module("reddit"));
beforeEach(inject(function(_redditService_, $httpBackend) {
// https://docs.angularjs.org/api/ngMock/function/angular.mock.inject
// "injected parameters can, optionally, be enclosed
// with underscores. These are ignored by the injector
// when the reference name is resolved."
redditService = _redditService_;
// https://docs.angularjs.org/api/ngMock/service/$httpBackend
// Fake HTTP backend implementation suitable for unit
// testing applications that use the $http service.
httpBackend = $httpBackend;
}));
it("should do something", function () {
httpBackend.whenGET("http://api.reddit.com/user/yoitsnate/submitted.json").respond({
data: {
children: [
{ data: { subreddit: "golang" } },
{ data: { subreddit: "javascript" } },
{ data: { subreddit: "golang" } },
{ data: { subreddit: "javascript" } }
]
}
});
redditService.getSubredditsSubmittedToBy("yoitsnate").then(function(subreddits) {
expect(subreddits).toEqual(["golang", "javascript"]);
});
httpBackend.flush();
});
});
describe('angularjs homepage todo list', function() {
it('should add a todo', function() {
browser.get('http://www.angularjs.org');
element(by.model('todoText')).sendKeys('write a protractor test');
$('[value="add"]').click();
var todoList = element.all(by.repeater('todo in todos'));
expect(todoList.count()).toEqual(3);
expect(todoList.get(2).getText()).toEqual('write a protractor test');
});
});
Potential bonuses:
Presentation by Anthony Panozzo / @panozzaj