SlideShare uma empresa Scribd logo
1 de 21
Testes Unitários de
JS com Jasmine e
Karma
Douglas Matoso
Por que fazer testes unitários no
frontend?
● Dá confiança em refatorações.
● Identifica bugs mais cedo.
● Ajuda a documentar o código.
● Ajuda a melhorar o design.
● É divertido :-D
Por 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 alterações no código
disparam os testes automaticamente.
● Poder gerar relatórios diversos (resumo dos testes, cobertura do
código testado, etc.)
● Isolar o frontend do backend, evitando interferências da rede, do
banco de dados, etc.
Objetivos
● Roda os testes em diferentes
navegadores com um comando.
● Modo watch.
● Permite gerar relatórios através de
plugins.
● Fácil integração 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 Gulp
var 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 contém
.toBeGreaterThan(value)
.toBeLessThan(value)
.toThrow()
// esta suíte não vai ser executada
xdescribe(‘Suite’, function () {
it(‘should do something’, function () {
...
});
it(‘should do something else’, function () {
...
});
});
Jasmine: skip e focus
describe(‘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 rotas
describe(‘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 fácil 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 opções
Factory.create(‘product’, { stock: 0 });
Factory.create(‘inactive-product’);
Factory.createList(10, ‘product’);
Fabricando dados com js-factories
// Definição da factory
Factory.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 view
it(‘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-jquery
it(‘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 matchers
toBeChecked()
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 Objects
it(‘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
THANK
YOU
FOR YOUR
TIME!

Mais conteúdo relacionado

Mais procurados

Node.js moduly a testovanie
Node.js moduly a testovanieNode.js moduly a testovanie
Node.js moduly a testovanieharcek
 
Rambler.iOS #8: Чистые unit-тесты
Rambler.iOS #8: Чистые unit-тестыRambler.iOS #8: Чистые unit-тесты
Rambler.iOS #8: Чистые unit-тестыRAMBLER&Co
 
ECMAscript 2015 aka ES6 : à la découverte du nouveau javascript
ECMAscript 2015 aka ES6 : à la découverte du nouveau javascriptECMAscript 2015 aka ES6 : à la découverte du nouveau javascript
ECMAscript 2015 aka ES6 : à la découverte du nouveau javascriptmatparisot
 
Java script.trend(spec)
Java script.trend(spec)Java script.trend(spec)
Java script.trend(spec)dynamis
 
HTTP Interceptors com AngularJS
HTTP Interceptors com AngularJSHTTP Interceptors com AngularJS
HTTP Interceptors com AngularJSRodrigo Branas
 
Java AWT Calculadora
Java AWT CalculadoraJava AWT Calculadora
Java AWT Calculadorajubacalo
 
Java Thread Cronometro
Java Thread CronometroJava Thread Cronometro
Java Thread Cronometrojubacalo
 
Unit Testing con Jest + Enzime para ReactJs
Unit Testing con Jest + Enzime para ReactJsUnit Testing con Jest + Enzime para ReactJs
Unit Testing con Jest + Enzime para ReactJsGabrielComas2
 
EJEMPLOS DESARROLLADOS
EJEMPLOS DESARROLLADOSEJEMPLOS DESARROLLADOS
EJEMPLOS DESARROLLADOSDarwin Durand
 
Aller plus loin avec Doctrine2
Aller plus loin avec Doctrine2Aller plus loin avec Doctrine2
Aller plus loin avec Doctrine2André Tapia
 
Hands-On Java web passando por Servlets, JSP, JSTL, JDBC, Hibernate, DAO, MV...
Hands-On Java web passando por  Servlets, JSP, JSTL, JDBC, Hibernate, DAO, MV...Hands-On Java web passando por  Servlets, JSP, JSTL, JDBC, Hibernate, DAO, MV...
Hands-On Java web passando por Servlets, JSP, JSTL, JDBC, Hibernate, DAO, MV...Mario Jorge Pereira
 
NrStage 사용하기
NrStage 사용하기NrStage 사용하기
NrStage 사용하기Yongwu Choi
 
Barcamp
BarcampBarcamp
Barcamprenelc
 
Юнит тестирование в Zend Framework 2.0
Юнит тестирование в Zend Framework 2.0Юнит тестирование в Zend Framework 2.0
Юнит тестирование в Zend Framework 2.0zfconfua
 
ECMAScript 6 im Produktivbetrieb
ECMAScript 6 im ProduktivbetriebECMAScript 6 im Produktivbetrieb
ECMAScript 6 im ProduktivbetriebSebastian Springer
 
JS programowanie obiektowe
JS  programowanie obiektoweJS  programowanie obiektowe
JS programowanie obiektowePiotr Czajkowski
 
C++ Programming - 13th Study
C++ Programming - 13th StudyC++ Programming - 13th Study
C++ Programming - 13th StudyChris Ohk
 

Mais procurados (19)

Node.js moduly a testovanie
Node.js moduly a testovanieNode.js moduly a testovanie
Node.js moduly a testovanie
 
Rambler.iOS #8: Чистые unit-тесты
Rambler.iOS #8: Чистые unit-тестыRambler.iOS #8: Чистые unit-тесты
Rambler.iOS #8: Чистые unit-тесты
 
Testování prakticky
Testování praktickyTestování prakticky
Testování prakticky
 
ECMAscript 2015 aka ES6 : à la découverte du nouveau javascript
ECMAscript 2015 aka ES6 : à la découverte du nouveau javascriptECMAscript 2015 aka ES6 : à la découverte du nouveau javascript
ECMAscript 2015 aka ES6 : à la découverte du nouveau javascript
 
Java script.trend(spec)
Java script.trend(spec)Java script.trend(spec)
Java script.trend(spec)
 
HTTP Interceptors com AngularJS
HTTP Interceptors com AngularJSHTTP Interceptors com AngularJS
HTTP Interceptors com AngularJS
 
Proxy & CGLIB
Proxy & CGLIBProxy & CGLIB
Proxy & CGLIB
 
Java AWT Calculadora
Java AWT CalculadoraJava AWT Calculadora
Java AWT Calculadora
 
Java Thread Cronometro
Java Thread CronometroJava Thread Cronometro
Java Thread Cronometro
 
Unit Testing con Jest + Enzime para ReactJs
Unit Testing con Jest + Enzime para ReactJsUnit Testing con Jest + Enzime para ReactJs
Unit Testing con Jest + Enzime para ReactJs
 
EJEMPLOS DESARROLLADOS
EJEMPLOS DESARROLLADOSEJEMPLOS DESARROLLADOS
EJEMPLOS DESARROLLADOS
 
Aller plus loin avec Doctrine2
Aller plus loin avec Doctrine2Aller plus loin avec Doctrine2
Aller plus loin avec Doctrine2
 
Hands-On Java web passando por Servlets, JSP, JSTL, JDBC, Hibernate, DAO, MV...
Hands-On Java web passando por  Servlets, JSP, JSTL, JDBC, Hibernate, DAO, MV...Hands-On Java web passando por  Servlets, JSP, JSTL, JDBC, Hibernate, DAO, MV...
Hands-On Java web passando por Servlets, JSP, JSTL, JDBC, Hibernate, DAO, MV...
 
NrStage 사용하기
NrStage 사용하기NrStage 사용하기
NrStage 사용하기
 
Barcamp
BarcampBarcamp
Barcamp
 
Юнит тестирование в Zend Framework 2.0
Юнит тестирование в Zend Framework 2.0Юнит тестирование в Zend Framework 2.0
Юнит тестирование в Zend Framework 2.0
 
ECMAScript 6 im Produktivbetrieb
ECMAScript 6 im ProduktivbetriebECMAScript 6 im Produktivbetrieb
ECMAScript 6 im Produktivbetrieb
 
JS programowanie obiektowe
JS  programowanie obiektoweJS  programowanie obiektowe
JS programowanie obiektowe
 
C++ Programming - 13th Study
C++ Programming - 13th StudyC++ Programming - 13th Study
C++ Programming - 13th Study
 

Destaque

Testes unitários como ferramentas de design de código
Testes unitários como ferramentas de design de códigoTestes unitários como ferramentas de design de código
Testes unitários como ferramentas de design de códigoPaula Grangeiro
 
Testando uma aplicação AngularJS utilizando o Karma
Testando uma aplicação AngularJS utilizando o KarmaTestando uma aplicação AngularJS utilizando o Karma
Testando uma aplicação AngularJS utilizando o KarmaHenrique Limas
 
Efficient JavaScript Unit Testing, JavaOne China 2013
Efficient JavaScript Unit Testing, JavaOne China 2013Efficient JavaScript Unit Testing, JavaOne China 2013
Efficient JavaScript Unit Testing, JavaOne China 2013Hazem Saleh
 
Unit testing JavaScript using Mocha and Node
Unit testing JavaScript using Mocha and NodeUnit testing JavaScript using Mocha and Node
Unit testing JavaScript using Mocha and NodeJosh Mock
 
An Introduction to Unit Testing
An Introduction to Unit TestingAn Introduction to Unit Testing
An Introduction to Unit TestingJoe Tremblay
 
Advanced Jasmine - Front-End JavaScript Unit Testing
Advanced Jasmine - Front-End JavaScript Unit TestingAdvanced Jasmine - Front-End JavaScript Unit Testing
Advanced Jasmine - Front-End JavaScript Unit TestingLars Thorup
 
Unit testing best practices
Unit testing best practicesUnit testing best practices
Unit testing best practicesnickokiss
 

Destaque (7)

Testes unitários como ferramentas de design de código
Testes unitários como ferramentas de design de códigoTestes unitários como ferramentas de design de código
Testes unitários como ferramentas de design de código
 
Testando uma aplicação AngularJS utilizando o Karma
Testando uma aplicação AngularJS utilizando o KarmaTestando uma aplicação AngularJS utilizando o Karma
Testando uma aplicação AngularJS utilizando o Karma
 
Efficient JavaScript Unit Testing, JavaOne China 2013
Efficient JavaScript Unit Testing, JavaOne China 2013Efficient JavaScript Unit Testing, JavaOne China 2013
Efficient JavaScript Unit Testing, JavaOne China 2013
 
Unit testing JavaScript using Mocha and Node
Unit testing JavaScript using Mocha and NodeUnit testing JavaScript using Mocha and Node
Unit testing JavaScript using Mocha and Node
 
An Introduction to Unit Testing
An Introduction to Unit TestingAn Introduction to Unit Testing
An Introduction to Unit Testing
 
Advanced Jasmine - Front-End JavaScript Unit Testing
Advanced Jasmine - Front-End JavaScript Unit TestingAdvanced Jasmine - Front-End JavaScript Unit Testing
Advanced Jasmine - Front-End JavaScript Unit Testing
 
Unit testing best practices
Unit testing best practicesUnit testing best practices
Unit testing best practices
 

Testes unitários de JS com Jasmine e Karma

  • 1. Testes Unitários de JS com Jasmine e Karma Douglas Matoso
  • 2. Por que fazer testes unitários no frontend?
  • 3. ● Dá confiança em refatorações. ● Identifica bugs mais cedo. ● Ajuda a documentar o código. ● Ajuda a melhorar o design. ● É divertido :-D Por quê?
  • 4. Tools of the trade
  • 5. ● 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 alterações no código disparam os testes automaticamente. ● Poder gerar relatórios diversos (resumo dos testes, cobertura do código testado, etc.) ● Isolar o frontend do backend, evitando interferências da rede, do banco de dados, etc. Objetivos
  • 6. ● Roda os testes em diferentes navegadores com um comando. ● Modo watch. ● Permite gerar relatórios através de plugins. ● Fácil integração com Gulp, Grunt e pipelines de CI (como Jenkins). Karma: o executador http://karma-runner.github.io
  • 7. 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’ ] . . .
  • 8. Karma e Gulp var 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
  • 9. 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
  • 10. 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 contém .toBeGreaterThan(value) .toBeLessThan(value) .toThrow()
  • 11. // esta suíte não vai ser executada xdescribe(‘Suite’, function () { it(‘should do something’, function () { ... }); it(‘should do something else’, function () { ... }); }); Jasmine: skip e focus describe(‘Suite’, function () { // apenas este teste será executado fit(‘should do something’, function () { ... }); it(‘should do something else’, function () { ... }); });
  • 12. 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
  • 13. 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 rotas describe(‘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’); }); });
  • 14. Melhorando a escrita dos testes
  • 15. // O que é mais fácil 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 opções Factory.create(‘product’, { stock: 0 }); Factory.create(‘inactive-product’); Factory.createList(10, ‘product’); Fabricando dados com js-factories // Definição da factory Factory.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
  • 16. describe(‘Form validations’, function () { beforeEach(function () { this.view = new AppView(); $(‘body’).prepend(this.view.render().el); }); afterEach(function () { this.view.remove(); }); Testando a view it(‘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’); });
  • 17. describe(‘Form validations’, function () { beforeEach(function () { loadFixtures(‘signup-form.html’); }); Custom matchers e HTML fixtures com jasmine-jquery it(‘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 matchers toBeChecked() toBeDisabled() toBeHidden() toBeVisible() toContainElement(jQuerySelector) toContainHtml(string) toContainText(string) toHandle(eventName) toHaveAttr(attributeName, attributeValue) toHaveClass(className) toHaveCss(css) toHaveText(string) toHaveHtml(string)
  • 18. describe(‘Form validations’, function () { beforeEach(function () { this.page = new SignupPage(); }); afterEach(function () { this.page.destroy(); }); Testes mais elegantes com Page Objects it(‘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’); });
  • 19. 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
  • 20. Show me the code! https://github.com/doug2k1/karma-jasmine-example