This is an old revision of the document!
Table of Contents
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
API Testing con Postman
Test scripts
En la propia documentación de Postman hay un montón de ejemplos de todo lo que es posible hacer con los test scripts de esta herramienta:
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 eclipse-temurin:21-jdk-alpine VOLUME /tmp EXPOSE 8080 COPY target/games-0.1.jar app.jar ENTRYPOINT ["java", "-jar", "app.jar"]
Docker compose
El siguiente es un ejemplo de un fichero docker-compose.yml listo para lanzar la API + Base de datos como contenedores docker
name: games services: games-db: image: mariadb:11.3.2 container_name: games-db restart: always environment: MYSQL_USER: games_user MYSQL_PASSWORD: games_password MYSQL_DATABASE: games MYSQL_PORT: 3306 MYSQL_ROOT_PASSWORD: test_root_password ports: - "3306:3306" healthcheck: test: [ CMD, healthcheck.sh, --connect, --innodb_initialized ] interval: 5s timeout: 3s retries: 3 networks: - network-games games-api: image: games-api container_name: games-api ports: - "8080:8080" depends_on: games-db: condition: service_healthy environment: SPRING_APPLICATION_JSON: '{ "spring.application.name": "games", "sever.port": "8080", "spring.jpa.hibernate.ddl-auto": "update", "spring.jpa.properties.hibernate.globally_quoted_identifiers": "true", "spring.datasource.url": "jdbc:mariadb://games-db:3306/games", "spring.datasource.username": "games_user", "spring.datasource.password": "games_password", "spring.jpa.database-platform": "org.hibernate.dialect.MariaDBDialect" }' networks: - network-games networks: network-games: 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 - develop jobs: newman: runs-on: ubuntu-latest steps: - uses: actions/checkout@master - uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: '21' - run: mvn package - run: docker build -t games-api . - run: docker compose up -d - run: npm install -g newman - run: newman run games.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

