Entornos de desarrollo

1º DAM/DAW - Curso 2026-2027

User Tools

Site Tools


apuntes:testing

Testing

Test unitarios

Se implementan para testear la unidad mínima de código, que normalmente serán funciones o métodos, de forma totalmente aislada del resto de la aplicación.

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

En este caso escribimos tests que permitan probar varios componentes juntos para verificar que trabajan bien cuando interactuan entre si.

  • 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

API Testing con Postman

Test scripts

Newman

Instalar Newman de forma global como comando en tu equipo, listo para ejecutarse directamente desde la consola (Si aún no tienes Node.js instalado, sigue estas instrucciones):

santi@zenbook:$ npm install -g newman

Cómo lanzar una colección de Postman con Newman:

santi@zenbook:$ newman run api-collection.postman_collection.json

Cómo lanzar una colección de Postman con Newman para un entorno determinado:

santi@zenbook:$ newman run -e localhost.postman_environment.json api-collection.postman_collection.json

Puedes encontrar una colección de ejemplo ya preparada con algunos scripts, lista para ser ejecutada con Newman aqui

API testing con Postman automatizado con GitHub Actions

Dockerfile

El primer paso será generar la imagen docker de la API que hemos desarrollado, para que podamos lanzarla luego como un contenedor docker

FROM node:22-alpine
WORKDIR /usr/src/app
COPY package*json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
ENV NODE_ENV=production
CMD ["node", "src/app.js"]

Docker compose

El siguiente es un ejemplo de un fichero docker-compose.yml listo para lanzar la API + Base de datos como contenedores docker

version: "3.4"
name: cities
services:
  db:
    image: mariadb:11.3.2
    container_name: cities-db
    environment:
      MARIADB_USER: 'user'
      MARIADB_PASSWORD: 'password'
      MARIADB_PORT: 3306,
      MARIADB_ROOT_PASSWORD: 'rootpassword'
    volumes:
      - ./db:/docker-entrypoint-initdb.d
    healthcheck:
      test: [ CMD, healthcheck.sh, --connect, --innodb_initialized]
      interval: 10s
      timeout: 5s
      retries: 3
    networks:
      - network1
  backend:
    image: cities-be
    container_name: cities-api
    ports:
      - "8080:8080"
    depends_on:
      db:
        condition: service_healthy
    networks:
      - network1
  frontend:
    image: cities-fe
    container_name: cities-ui
    ports:
      - "1234:1234"
    networks:
      - network1
networks:
  network1:
    driver: bridge

GitHub action

Por último, configuramos un workflow de GitHub Actions que hará lo siguiente:

  • Generamos la imagen docker de nuestra API
  • Lanzará nuestra API + Base de datos como contenedores docker
  • Instalará Newman
  • Lanzará la colección con los tests con Newman sobre la API que se acaba de lanzar
name: Integration tests with Newman

on:
  pull_request:
    branches:
      - main

jobs:
  newman:
    runs-on: ubuntu-latest
    
    strategy:
      matrix:
        node-version: [20.x, 22.x, 24.x]
    
    steps:
      - uses: actions/checkout@master
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      - run: npm install
      - run: docker build -t cities-api .
      - run: docker compose up -d
      - run: npm install -g newman
      - run: newman run cities.postman_collection.json -e local.postman_environment.json

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-2026 Santiago Faci

apuntes/testing.txt · Last modified: by Santiago Faci