Entornos de desarrollo

1º DAM/DAW - Curso 2024-2025

User Tools

Site Tools


apuntes:testing

Testing

Test unitarios

Clases de utilidad

const expect = require('chai').expect;
const { getDaysFromNow, getDays} = require('../../dateUtils');
 
beforeAll(() => {
    jest.useFakeTimers();
    jest.setSystemTime(new Date('2025-02-13'));
});
 
afterAll(() => {
    jest.useRealTimers();
});
 
describe('dateUtils', () => {
    // TODO Añadir casos de prueba para la función getYearsFromNow
 
    it('getDaysFromNow', () => {
        let days = getDaysFromNow(new Date('2025-02-12'));
        expect(days).equal(1);
 
        days = getDaysFromNow(new Date('2025-01-13'));
        expect(days).equal(31);
    });
 
    it('getDays', () => {
        let days = getDays(new Date('2025-01-12'), new Date('2025-01-15'));
        expect(days).equal(3);
 
        days = getDays(new Date('2025-01-01'), new Date('2024-01-01'));
        expect(days).equal(366);
 
        days = getDays(new Date('2024-01-01'), new Date('2024-01-01'));
        expect(days).equal(0);
    });
});

Capa controller

const httpMocks = require('node-mocks-http');
const { describe, it, expect, afterEach } = require('@jest/globals');
 
jest.mock('../../service/cities');
 
const cityController = require('../../controller/cities');
 
const cityService = require('../../service/cities');
const mockedFindCities = jest.spyOn(cityService, 'findCities');
const mockedRegisterCity = jest.spyOn(cityService, "registerCity");
const { mockCityArray, mockCityToPost, mockCityResponse, mockCityToRegister } = require('./mocks/cities');
 
afterEach(() => {
    jest.clearAllMocks();
});
 
describe('cities', () => {
    it('GET /cities should get a city list', async () => {
        const response = httpMocks.createResponse();
        const request = httpMocks.createRequest();
        request.app = {};
        request.app.conf = {};
        request.path = '/cities';
 
        const mockedCityList = jest.fn(async () => {
            return mockCityArray;
        });
        mockedFindCities.mockImplementation(mockedCityList);
 
        await cityController.getCities(request, response);
        expect(mockedFindCities).toHaveBeenCalledTimes(1);
        expect(response.statusCode).toEqual(200);
        expect(response._isEndCalled()).toBeTruthy();
        expect(response._getJSONData().length).toEqual(5);
    });
 
    it('POST /cities should register a new city', async () => {
        const response = httpMocks.createResponse();
        const request = httpMocks.createRequest();
        request.app = {};
        request.app.conf = {};
        request.path = '/cities';
        request.body = mockCityToRegister;
 
        const mockedRegisterCityResponse = jest.fn(async () => {
            return mockCityResponse;
        });
        mockedRegisterCity.mockImplementation(mockedRegisterCityResponse);
 
        await cityController.postCity(request, response);
        expect(mockedRegisterCity).toHaveBeenCalledTimes(1);
        expect(response.statusCode).toEqual(201);
        expect(response._isEndCalled()).toBeTruthy();
        expect(response._getJSONData().id).toEqual(1);
        expect(response._getJSONData().name).toEqual('Zaragoza');
        expect(response._getJSONData().population).toEqual(700000);
        expect(response._getJSONData().altitude).toEqual(200);
        expect(response._getJSONData().age).toEqual(5);
        expect(response._getJSONData().area).toEqual(734734);
        expect(response._getJSONData().density).toEqual(2373);
        expect(response._getJSONData().foundationDate).toEqual('2020-12-01');
    });
});
. . .
"scripts": {
    "unit-test": "jest src/test/unit",
    . . .
. . .
santi@zenbook:$ npm install -g jest

Test de integración

  • Caso de uso: Ok (200 OK)
const chai = require('chai');
const expect = chai.expect;
const chaiHttp = require('chai-http');
 
const app = require('../../app').app;
 
chai.use(chaiHttp);
chai.should();
 
describe('cities', () => {
  describe('GET /cities', () => {
    it('should get all cities', (done) => {
      chai.request(app)
        .get('/cities')
        .end((error, response) => {
           response.should.have.status(200);
           response.body.should.be.a('array');
           expect(response.body[0]).to.have.property('name');
           expect(response.body[0]).to.have.property('altitude');
           expect(response.body[0]).to.have.property('population');
           expect(response.body[0]).to.have.property('foundationDate');
           expect(response.body[0]).to.have.property('age');
           expect(response.body[0]).to.have.property('area');
           expect(response.body[0]).to.have.property('density');
 
           expect(response.body[0].name).to.equal('Zaragoza');
           expect(response.body[1].name).to.equal('Madrid');
           done();
         });
    });
  });
});
  • Caso de uso: Registro (201 Created)
it('should register a new city', (done) => {
  chai.request(app)
    .post('/cities')
    .send({
      name: 'cityName',
      population: 100,
      altitude: 200,
      foundationDate: '2005-10-10',
      area: 1000
    })
    .end((error, response) => {
      response.should.have.status(201);
      expect(response.body).to.have.property('id');
      expect(response.body).to.have.property('name');
      expect(response.body).to.have.property('population');
      expect(response.body).to.have.property('altitude');
      expect(response.body).to.have.property('foundationDate');
      expect(response.body).to.have.property('age');
      expect(response.body).to.have.property('area');
      expect(response.body).to.have.property('density');
      done();
    });
});
  • Caso de uso: Validación (400 Bad Request)
it('validation should fail because name is mandatory', (done) => {
  chai.request(app)
    .post('/cities')
    .send({
      population: 100,
      altitude: 200,
      foundationDate: '2005-10-10',
      area: 1000
    })
    .end((error, response) => {
      response.should.have.status(400);
      expect(response.body.status).to.equal('bad-request');
      expect(response.body.message).to.equal('name field is mandatory');
      done();
    });
});
. . .
"scripts": {
    "integration-test": "mocha src/test/integration --exit",
    . . .
. . .

También podemos aprovechar para modificar el script test de forma que lance todos los tests: unitarios y de integración:

. . .
"scripts": {
    . . .
    "test": "npm run unit-test && npm run integration-test",
    . . . 
. . .

Asi, podremos lanzar todos los tests solo con npm test

Crear un entorno de pruebas con Docker

Colocaremos en la carpeta db del proyecto el script SQL que creará la base de datos y las tablas. También podemos añadir las sentencias INSERT que queramos para preparar datos según los casos de nuestros tests de integración.

Podemos crear un fichero docker-compose.dev.yaml con la siguiente configuración:

version: "3.4"
name: cities
services:
  db:
    image: mariadb:11.3.2
    container_name: cities-db-dev
    environment:
      MYSQL_USER: 'user'
      MYSQL_PASSWORD: 'password'
      MYSQL_PORT: 3306,
      MYSQL_ROOT_PASSWORD: 'rootpassword'
    ports:
      - "3306:3306"
    volumes:
      - ./db:/docker-entrypoint-initdb.d

Y podremos lanzar nuestro entorno de pruebas con el siguiente comando:

santi@zenbook:$ docker compose -f docker.compose.dev.yaml up -d

Para destruir el entorno:

santi@zenbook:$ docker compose -f docker.compose.dev.yaml down

También se puede simplemente detener:

santi@zenbook:$ docker compose -f docker.compose.dev.yaml stop

O bien iniciar si está detenido:

santi@zenbook:$ docker compose -f docker.compose.dev.yaml start

Proyectos de ejemplo

En cities puedes encontrar un proyecto con tests unitarios y de integración implementados. También está lo necesario para crear un entorno de pruebas y lanzar todos los tests utilizando dicho entorno.


© 2025 Santiago Faci

apuntes/testing.txt · Last modified: 2025/03/30 19:55 by Santiago Faci