Tecnologia
Conectando projetos Cypress ao banco de dados
6 minutos de leitura
Então, em um belo dia, alguém te fala que a automação em que vocês estão trabalhando precisa se conectar ao banco de dados por algum motivo, seja por conta de alguma validação, seja para fazer setup ou teardown para algum teste ou uma suíte de testes. Enfim, a missão está dada. E aí? Por onde se começa? Vem comigo que, no caminho, eu te explico! Temos uma série de passos para chegar lá. Para alguns, você vai precisar de ajuda, mas vai dar certo.
Começando do começo, identificando o banco…
É comum a gente ouvir sobre o banco por aqui e ali mas nem sempre a gente sabe responder se esse banco é um sql ou um nosql, se é um oracle? sql server? postgresql? mysql?
Alguém vai ter essa informação, chama teu colega, teu LT, até teu agilista vale. Essa informação é importante porque precisaremos adicionar bibliotecas diferentes conforme o banco que precisarmos acessar – já já a gente fala disso.
Feito?! Então, bora pros acessos
Essa parte provavelmente você vai precisar de ajuda, precisamos saber como nos conectar com esse banco. Geralmente as informações que precisamos são usuário, senha, banco de dados, servidor e porta
. É importante também que saber como nossa máquina e o CI/CD vão fazer para se conectar com o banco. Precisa de uma VPN? Um proxy? Ou está tudo na cloud e só precisamos de uma URL?
Precisamos escolher uma biblioteca
É comum pensarmos que, quando vamos conectar nosso projeto de automação ao banco, isso é feito apenas através do Cypress ou que o trecho de código que encontramos nas nossas pesquisas sozinho vai fazer toda a mágica.
Na verdade, nós utilizamos uma biblioteca na linguagem de programação do projeto, JavaScript nos nossos exemplos, para interagir com o banco. Não é exatamente o Cypress que se conecta e aquele trecho, por mais que esteja certo, precisa da biblioteca, fechou? Bom, vamos ter várias opções para adicionar no projeto, qual delas escolher?
Provavelmente vamos encontrar mais de uma. É legal que procuremos sempre pelas mais atualizadas, elas quase sempre vão te dar menos problemas e serão mais performáticas.
Segue uma lista com as quais já tive contato:
mssql – https://www.npmjs.com/package/mssql SQL Server
mongodb – https://www.npmjs.com/package/mongodb Mongo DB
mysql – https://www.npmjs.com/package/mysql MySQL
Bora adicionar ela ao projeto. Nós vamos abrir um terminal na pasta do projeto. No VSCode, o comando é:
CTRL+’
No terminal vamos digitar:
npm install <nome-da-biblioteca>
Depois de instalado o arquivo package.json
terá a dependência adicionada.
Feito! Daqui pra frente vamos conectar com o banco e criar funções para as interações.
Conectando no banco
Bom, agora vamos usar os acessos que você conseguiu ali em cima para conectar no banco.
Vamos criar uma pasta no caminho cypress/support/
chamada database
e lá criar o arquivo mssql.js
. O conteúdo dele deve ficar assim:
// import da biblioteca mssql
const mssql = require('mssql')
// criação de um objeto com as informações
// da conexão que vamos fazer
const sqlConfig = {
user: 'usr_test',
password: '277b48e709c@',
database: 'CompanyDatabase',
server: 'localhost',
port: 1433,
options: {
trustServerCertificate: true
}
}
// uma função para realizar a conexão
// com o banco, que recebe por parâmetro
// a query a ser executada
async function execute(query) {
// criação de um pool de conexão com as configurações
// do nosso banco
let pool = new mssql.ConnectionPool(sqlConfig)
try {
// tentativa de conexão com o banco
await pool.connect()
// execução da query passada como parâmentro
const result = await pool.request().query(query)
// retorna os dados obtidos pela query
return result
} catch (error) {
// print no console caso aconteça algum erro
console.log(error)
} finally {
// sempre fecha a conexão após utilizá-la
if (pool) {
pool.close()
}
}
}
module.exports = { execute }
A título de teste, podemos adicionar mais essa função no final do arquivo: (remover após testar)
// testando a função connect com um query simples
execute('select * from Employee')
.then((r) => { console.log('result:', JSON.stringify(r)) })
E executar o mesmo no terminal:
$node .\\mssql.js
result: {"recordsets":[[]],"recordset":[],"output":{},"rowsAffected":[0]}
Esse script é funcional para realizar as operações básicas no banco de dados e utilizarmos neste artigo. Vale lembrar que as configurações de banco expostas no código não são uma boa prática. E, caso haja a necessidade de nos conectarmos a mais de um banco de dados, é preciso incrementarmos o controle do pool de conexões.
A seguir, vamos criar algumas tasks no Cypress para exemplificar o uso em nossos testes.
Cadastrando tasks e commands para interagir com o banco
Tasks
O seu arquivo cypress.config.js
deve ser parecido com esse:
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
},
},
});
Para cadastrar as tasks no arquivo cypress.config.js
adicionamos o seguinte código:
const { defineConfig } = require("cypress");
// importa nossa função que interage com o banco
const { execute } = require('./cypress/support/database/mssql')
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
on('task', {
// define uma task que irá executar nossas queries no banco configurado
async executeStatementDatabase(query) {
// chama nossa função criada anteriormente e retorna seu resultado
const result = await execute(query)
return result
}
})
},
},
});
E por fim, utilizando essa cy.task no teste o código do teste fica assim:
describe('Database Tests', () => {
it('Should perform a query in the database.', () => {
cy.task('executeStatementDatabase', 'select * from Employee').should((results) => {
expect(results.recordset).to.exist
})
})
})
A cy.task é a forma que temos de chamar código node
no nosso projeto de automação. Porém, ficar executando em vários testes pode gerar problemas na hora de manutenção (isso se aplica para mais coisas). Por exemplo, imagine que precisamos fazer vários testes com o banco e escrevemos todos nesse mesmo formato:
describe('Database Tests', () => {
it('Should perform a query in the database', () => {
cy.task('executeStatementDatabase', 'select * from Employee').should((results) => {
expect(results.recordsets).to.exist
})
})
...
...
...
it('Should perform a query in the database100', () => {
cy.task('executeStatementDatabase', ('select * from Employee')).should((results) => {
expect(results.recordsets).to.exist
})
})
})
Se um belo dia o nome da tabela ou as suas colunas mudarem, teremos que ir em todos os pontos onde chamamos o cy.task e alterar a query que estamos passando por parâmetro. Para resolver esse problema, vamos criar cy.commands.
Commands
Os commands são criados geralmente no caminho cypress/support/commands/
e são “registrados” no arquivo cypress/support/e2e.js
através de um import no arquivo em que eles forem definidos.
No nosso caso, o command que obtém todos os usuários da tabela Employee ficaria assim:
Cypress.Commands.add('getAllEmployees', () => {
return cy.task('executeStatementDatabase', `SELECT * FROM Employee`)
})
E o nosso teste passaria a chamar o command invés da task.
describe('Database Tests', () => {
it('Should perform a query in the database', () => {
cy.getAllEmployees().should(result => {
expect(result.recordsets).to.exist
})
})
})
Melhorou, não é?! Agora, quando for preciso, teremos que alterar a query somente em um lugar.
Em relação às queries, provavelmente você vai encontrar diferentes abordagens, principalmente quanto à organização delas no projeto.
Elas podem continuar no command ou, no caso de queries mais complexas, podemos criar funções que recebam parâmetros e retornem uma string representando a query concatenada.
Vamos criar um novo comando, dessa vez para criar um novo employee, e aí eu mostro essas abordagens.
Direto no command:
Cypress.Commands.add('createEmployee', (employee) => {
cy.task('executeStatementDatabase',
`INSERT INTO Employee (EmployeeID, FirstName, LastName, Department, Salary)
VALUES (
'${employee.employeeId}',
'${employee.firstName}',
'${employee.lastName}',
'${employee.department}',
'${employee.salary}'
)`
).should(result => {
expect(result.rowsAffected).to.equal(1);
})
})
Com uma função que retorna a query:
function newEmployeeQuery(employee) {
return `INSERT INTO Employee (EmployeeID, FirstName, LastName, Department, Salary)
VALUES (
'${employee.employeeId}',
'${employee.firstName}',
'${employee.lastName}',
'${employee.department}',
'${employee.salary}'
)`
}
export default newEmployeeQuery
Command chamando a função que retorna a query e passa para task o que precisa ser executado no banco:
const newEmployeeQuery = require('../queries/newEmployee')
Cypress.Commands.add('createEmployeeWithQueryFunction', (employee) => {
cy.task('executeStatementDatabase',
newEmployeeQuery(employee)
).should(result => {
expect(result.rowsAffected[0]).to.equal(1);
})
})
Nosso teste:
it('Should insert a new employee', () => {
const employee = {
firstName: 'John',
lastName: 'Doe',
department: 'Sales',
salary: 4000.0
}
cy.createEmployee(employee)
cy.createEmployeeWithQueryFunction(employee)
})
Segue abaixo o repositório como exemplo de conexões com outros bancos de dados. A estrutura dos projetos é a mesma. Se o banco de dados que você está utilizando ainda não possuir um exemplo, crie uma issue no repositório para que possamos providenciar.
https://github.com/EullerLisowski/cypress-db-conn