Jasmine e Sequelize: Uma introdução
Criar o projeto
Vamos usar o npm para criar o projeto:
$ npm init
Siga as instruções. Após executar o comando será criado um arquivo package.json semelhante a esse:
{
"name": "jasmime-and-sequelize.js",
"version": "1.0.0",
"description": "Sample project to use Sequelize and Jasmine.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Dmitry Rocha <blog@dmitryrck.com>",
"license": "MIT"
}
Vamos nessa etapa ainda colocar os pacotes de dependência:
$ npm install --save sequelize pg pg-hstore
$ npm install --save-dev jasmine-node
Realmente não sei o porquê dele precisar do hstore mesmo eu não usando nada além do PostgreSQL básico.
Passando o --save ou --save-dev para o npm install automaticamente colocará os pacotes como dependências no package.json, respectivamente para o ambiente padrão e para o ambiente de desenvolvimento.
O package.json deve ter ficado semelhante a:
{
"name": "jasmime-and-sequelize.js",
"version": "1.0.0",
"description": "Sample project to use Sequelize and Jasmine.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Dmitry Rocha <blog@dmitryrck.com>",
"license": "MIT",
"dependencies": {
"pg": "^4.3.0",
"pg-hstore": "^2.3.2",
"sequelize": "^3.1.0"
},
"devDependencies": {
"jasmine-node": "^1.14.5"
}
}
Precisaremos editar esse arquivo para adicionar a linha de execução do teste, isso é opcional, mas extremamente recomendado:
"scripts": {
"test": "jasmine-node spec"
},
Criando o ambiente de teste
Vamos seguir o desenvolvimento da app criando os testes.
Existe uma boa prática de criar um arquivo de helper que carrega todo o ambiente e faz as devidas configurações para o ambiente de teste. O nosso arquivo se chamará spec/spec.helper.js:
process.env.NODE_ENV = "test";
global.db = require("../models");
Como fluxo normal do bdd vamos rodar o teste:
$ npm test
> jasmime-and-sequelize.js@1.0.0 test /home/dmitry/Dev/jasmime-and-sequelize.js
> jasmine-node spec
Exception loading helper: /home/dmitry/Dev/jasmime-and-sequelize.js/spec/spec.helper.js
{ [Error: Cannot find module '../models'] code: 'MODULE_NOT_FOUND' }
Então… não achou o ../models, vamos criá-lo.
Mas primeiro: se você executar um require de um diretório no nodejs, ele procurará por um arquivo index.js.
O nosso models/index.js deve ser algo como:
var fs = require("fs");
var DataTypes = require('sequelize');
var sequelize = new DataTypes('seq_' + process.env.NODE_ENV, 'postgres', '', { dialect: 'postgres' });
var db = {};
fs
.readdirSync(__dirname)
.filter(function(file) {
return(file.indexOf(".") !== 0) && (file !== "index.js");
})
.forEach(function(file) {
var model = sequelize["import"](__dirname + '/' + file);
db[model.name] = model;
});
db.DataTypes = DataTypes;
db.sequelize = sequelize;
module.exports = db;
A tarefa básica desse index.js é percorer o diretório no qual ele reside e carregar os módulos, no nosso caso, usando o import do próprio sequelize, que, a propósito, faz cache do arquivos carregados.
Nessa etapa não existe nenhum teste, então está tudo passando.
Primeiro teste
Finalmente escreveremos os testes :D
Arquivo spec/models/task.spec.js:
describe("Task", function() {
var Task = global.db.task;
it("should create", function(done) {
Task.create({ title: 'a title' }).then(function(task) {
expect(task.titulo).toBe('a title');
done();
});
});
});
Claro, o teste quebra, para fazê-lo passar tem que criar o módulo relacionado, arquivo models/task.js:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('task', {
title: DataTypes.STRING,
menu: DataTypes.BOOLEAN
});
};
É de se esperar que o teste passe, mas a base de dados e a tabela não existem.
Existem algumas de formas contornar isso: uma delas é criando tudo usando sql de fora do sequelize/nodejs/jasmine, particularmente acho essa uma tarefa meio árdua demais, então vamos deixar o mais automatizado possível:
Crie a base de dados por fora (não encontrei forma de criar de dentro do próprio ambiente):
$ createdb seq_test
Modifique o arquivo spec/models/task.spec.js:
describe("Task", function() {
var Task = global.db.task;
it("should create", function(done) {
Task.sync({ force: true }).then(function(task) {
Task.create({ title: 'a title' }).then(function(task) {
expect(task.title).toBe('a title');
done();
});
});
});
});
Eu deixei esse código por fora da implementação inicial para chamar a atenção para o sync({ force: true }).
O sync() irá criar o banco de dados, tabelas e colunas de acordo com os models.
Já { force: true } irá apagar o banco de dados antes de criá-lo, para o nosso caso que é um ambiente de teste isso não é um problema muito grande, apesar de não ser o ideal.
O código como de costume está em: https://github.com/dmitryrck/jasmime-and-sequelize.js, além dessa estrutura aqui feita eu fiz uma pequena refatoração no teste.
Ah, e só para avisar eu estou aprendendo a desenvolver com javascript, algumas abordagens não foram feitas de forma ideal, estão apenas funcionais, então caso encontre algum erro e tenha alguma sugestão não exite em avisar-me ;)
Update
Parei de usar o pacote npm jasmine-node e passei a usar o jasmine, aparentemente a única diferença é que jasmine-node não é mais mantido;
Deixei de usar o done nos parâmetros dos testes que fazem hit no banco de dados, o done como parâmetro indica um teste que pode ser executado simultaneamente com outros. (ainda não estou muito certo disso).
Isso só é importante para testes que não usam o banco de dados, por exemplo: criar um teste para um helper, nesse caso bastaria eu instanciar um objeto, mas não precisaria salvar no banco de dados.
Veja as principais diferenças neste commit: cfc82cd7ccfab80cd5a7506f0c7824cc2d7d9486.