Jasmine e Sequelize: Uma introdução

Introdução ao Sequelize com testes automatizados com Jasmine

#dev, #javascript

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.

{ 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.

Referências