Testes unitários de JS com Jasmine e Karma

21
Testes Unitários de JS com Jasmine e Karma Douglas Matoso

Transcript of Testes unitários de JS com Jasmine e Karma

Testes Unitrios de JS com Jasmine e KarmaDouglas Matoso

Por que fazer testes unitrios no frontend?

D confiana em refatoraes.Identifica bugs mais cedo.Ajuda a documentar o cdigo.Ajuda a melhorar o design. divertido :-DPor qu?

Tools of the trade

Rodar os testes em diferentes navegadores (inclusive headless, como PhantomJS) com apenas um comando.Rodar os testes rapidamente (em segundos) e frequentemente.Rodar os testes em modo watch, onde alteraes no cdigo disparam os testes automaticamente.Poder gerar relatrios diversos (resumo dos testes, cobertura do cdigo testado, etc.)Isolar o frontend do backend, evitando interferncias da rede, do banco de dados, etc.Objetivos

Roda os testes em diferentes navegadores com um comando.Modo watch.Permite gerar relatrios atravs de plugins.Fcil integrao com Gulp, Grunt e pipelines de CI (como Jenkins).Karma: o executador

http://karma-runner.github.io

Karma: config e resultado// karma.conf.js

. . .frameworks: [jasmine],files: [ app/vendor/**/*.js, app/js/**/*.js, test/specs/**/*.js],reporters: [ progress, html, coverage],browsers: [ Chrome, PhantomJS]. . .

Karma e Gulpvar KarmaServer = require('karma').Server, KARMA_CONF_PATH = __dirname + '/karma.conf.js';

gulp.task('test', function (done) { var server = new KarmaServer({ configFile: KARMA_CONF_PATH, browsers: ['Chrome'], singleRun: false }, done); server.start();});Sem Gulp

> karma start

> karma start --single-run

Com Gulp

> gulp test

> gulp tdd

> gulp test-ci

describe(What is being tested, function () { describe(context, function () { beforeEach(function () { ... });

it(should do something, function () { ... });

it(should do something else, function () { ... }); });});Jasmine: o framework

http://jasmine.github.io

it(should sum the numbers, function () { var result = sum(1,2,3); expect(result).toEqual(6);});

it(should return something, function () { var result = foo(); expect(result).not.toBeNull();});Jasmine: Matchers.toEqual(value).toBeNull().toBeDefined().toMatch(regex ou string).toBeTruthy(value) // cast para boolean.toBeFalsy(value) // cast para boolean.toContain(value) // array contm.toBeGreaterThan(value).toBeLessThan(value).toThrow()

// esta sute no vai ser executadaxdescribe(Suite, function () { it(should do something, function () { ... });

it(should do something else, function () { ... });});Jasmine: skip e focusdescribe(Suite, function () { // apenas este teste ser executado fit(should do something, function () { ... });

it(should do something else, function () { ... });});

describe(Product model, function () { beforeEach(function () { this.model = new Product({ id: 123 }); this.server = sinon.fakeServer.create(); this.server.respondWith(GET, /products/123, JSON.stringify({ description: JavaScript Book, price: 19.99 })]); });

afterEach(function () { this.server.restore(); });

it(should fetch product data, function () { this.model.fetch(); this.server.respond(); expect(this.model.get(description)) .toEqual(Javascript Book); });});Sinon: o servidor (fake)http://sinonjs.org

var TestRouter = function () { init: function () { this.server = sinon.fakeServer.create({ respondImmediately: true }); }, enableRoute: function (route) { this.server.respondWith(route.method, route.url, route.response); }})();

------------------------------------------var ProductRoute = { main: { method: GET, url: /products\/(\d+)/, response: JSON.stringify({ description: JavaScript Book, price: 19.99 }) }};Sinon: classe de rotasdescribe(Product model, function () { beforeEach(function () { this.model = new Product({ id: 123 }); TestRouter.init(); TestRouter.enableRoute(ProductRoute.main);

afterEach(function () { TestRouter.restore(); });

it(should fetch product data, function () { this.model.fetch(); expect(this.model.get(description)) .toEqual(Javascript Book); });});

Melhorando a escrita dos testes

// O que mais fcil escrever?// Assim:response: JSON.stringify({ id: 1, description: Product Test, price: 9.99, active: true, stock: 10 })

// Ou assim:response: JSON.stringify(Factory.create(product))

// Mais opesFactory.create(product, { stock: 0 });

Factory.create(inactive-product);

Factory.createList(10, product);Fabricando dados com js-factories// Definio da factoryFactory.define(product, function (attrs) { return _.extend({ id: this.sequence(id), description: Product Test, price: 9.99, active: !this.is(inactive), stock: 10 }, attrs);});https://github.com/matthijsgroen/js-factories

describe(Form validations, function () { beforeEach(function () { this.view = new AppView(); $(body).prepend(this.view.render().el); });

afterEach(function () { this.view.remove(); });Testando a viewit(should validate phone number, function () { var phoneInput = $(#phoneInput), submitBtn = $(#form input:submit), alert;

phoneInput.val(abc); submitBtn.click(); alert = $(.alert-danger);

expect(alert.is(:visible)).toBeTruthy(); expect(alert.text()).toMatch(phone is invalid);});

describe(Form validations, function () { beforeEach(function () { loadFixtures(signup-form.html); });

Custom matchers e HTML fixtures com jasmine-jqueryit(should validate phone number, function () { var phoneInput = $(#phoneInput), submitBtn = $(#form input:submit), alert;

phoneInput.val(abc); submitBtn.click(); alert = $(.alert-danger);

expect(alert).toBeVisible(); expect(alert).toHaveText(phone is invalid);});

https://github.com/velesin/jasmine-jquery// Alguns novos matcherstoBeChecked() toBeDisabled() toBeHidden() toBeVisible() toContainElement(jQuerySelector) toContainHtml(string) toContainText(string) toHandle(eventName) toHaveAttr(attributeName, attributeValue) toHaveClass(className) toHaveCss(css) toHaveText(string) toHaveHtml(string)

describe(Form validations, function () { beforeEach(function () { this.page = new SignupPage(); });

afterEach(function () { this.page.destroy(); });

Testes mais elegantes com Page Objectsit(should validate phone number, function () { var alert;

this.page.setPhoneNumber(abc); this.page.submitForm(); alert = this.page.getAlert();

expect(alert).toBeVisible(); expect(alert).toHaveText(phone is invalid);});

var SignupPage = Class.extend({ init: function () { this.view = new AppView(); $(body).prepend(this.view.render().el); },

destroy: function () { this.view.remove(); },

setPhoneNumber: function (value) { $(#phoneInput).val(value); },

submitForm: function () { $(#form input:submit).click(); },

getAlert: function () { return $(.alert-danger); }});Testes mais elegantes com Page Objects

http://martinfowler.com/bliki/PageObject.html

Show me the code!https://github.com/doug2k1/karma-jasmine-example

THANKYOUFOR YOURTIME!