Entornos de desarrollo

1º DAM/DAW - Curso 2024-2025

User Tools

Site Tools


apuntes:testing

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
apuntes:testing [2024/10/12 18:30] Santiago Faciapuntes:testing [2025/03/30 19:55] (current) Santiago Faci
Line 7: Line 7:
  
 ===== Test unitarios ===== ===== Test unitarios =====
 +
 +==== Clases de utilidad ====
 +
 +<code javascript>
 +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);
 +    });
 +});
 +</code>
 +
 +==== Capa controller ====
 +
 +<code javascript>
 +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');
 +    });
 +});
 +</code>
 +
 +<code javascript>
 +. . .
 +"scripts": {
 +    "unit-test": "jest src/test/unit",
 +    . . .
 +. . .
 +</code>
 +
 +<code bash>
 +santi@zenbook:$ npm install -g jest
 +</code>
  
 ===== Test de integración ===== ===== Test de integración =====
  
-----+  * Caso de uso: **Ok (200 OK)**
  
-(c2024 Santiago Faci+<code javascript> 
 +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(); 
 +         }); 
 +    }); 
 +  }); 
 +}); 
 +</code> 
 + 
 +  * Caso de uso: **Registro (201 Created)** 
 + 
 +<code javascript> 
 +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(); 
 +    }); 
 +}); 
 +</code> 
 + 
 +  * Caso de uso: **Validación (400 Bad Request)** 
 + 
 +<code javascript> 
 +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(); 
 +    }); 
 +}); 
 +</code> 
 + 
 +<code javascript> 
 +. . . 
 +"scripts":
 +    "integration-test": "mocha src/test/integration --exit", 
 +    . . . 
 +. . . 
 +</code> 
 + 
 +También podemos aprovechar para modificar el script ''test'' de forma que lance todos los tests: unitarios y de integración: 
 + 
 +<code javascript> 
 +. . . 
 +"scripts":
 +    . . . 
 +    "test": "npm run unit-test && npm run integration-test", 
 +    . . .  
 +. . . 
 +</code> 
 + 
 +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: 
 + 
 +<code yaml> 
 +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 
 +</code> 
 + 
 +Y podremos lanzar nuestro entorno de pruebas con el siguiente comando: 
 + 
 +<code bash> 
 +santi@zenbook:$ docker compose -f docker.compose.dev.yaml up -d 
 +</code> 
 + 
 +Para destruir el entorno: 
 + 
 +<code bash> 
 +santi@zenbook:$ docker compose -f docker.compose.dev.yaml down 
 +</code> 
 + 
 +También se puede simplemente detener: 
 +<code bash> 
 +santi@zenbook:$ docker compose -f docker.compose.dev.yaml stop 
 +</code> 
 + 
 +O bien iniciar si está detenido: 
 + 
 +<code bash> 
 +santi@zenbook:$ docker compose -f docker.compose.dev.yaml start 
 +</code> 
 + 
 +====== Proyectos de ejemplo ====== 
 + 
 +En [[https://github.com/codeandcoke/cities|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. 
 + 
 +----
  
 +(c) 2025 Santiago Faci
apuntes/testing.1728757855.txt.gz · Last modified: 2024/10/12 18:30 by Santiago Faci