imagen

TDD - Factories

El patron factory nos ayuda a crear estructura de datos de forma ms sencilla, si bien normalmente se aplica para funcionalidades, también nos podemos apoyar de este principio para simplificar la creación de escenarios de nuestros tests.

Primero veamos un ejemplo sin aplicar este principio y luego el resultado intentando aplicarlo.

Test sin aplicar Factorías ni Builders

import { LocationFilter } from "./original";

describe("Location filter", () => {
  let students = [];
  let classes = [];
  let locationFilter: LocationFilter;
  beforeEach(() => {
    students = [
      {
        id: 1,
        studentName: "Jhon",
        classId: 1,
        origin: "Math",
        publicNotes: [{ id: 1, content: "public" }],
        privateNotes: [{ id: 2, content: "private" }],
      },
      {
        id: 2,
        studentName: "Juliana",
        classId: 2,
        origin: "Spanish",
        publicNotes: [{ id: 1, content: "public" }],
        privateNotes: [],
      },
      {
        id: 3,
        studentName: "Dinwell",
        classId: 3,
        origin: "Science",
        publicNotes: [{ id: 1, content: "public" }],
        privateNotes: [],
      },
    ];
    classes = [
      {
        id: 1,
        name: "Math",
        location: "floor a1",
        system: "part time",
        credits: 200,
        level: "grade a",
      },
      {
        id: 2,
        name: "Spanish",
        location: "floor b1",
        system: "full time",
        credits: 200,
        level: "grade b",
      },
      {
        id: 3,
        name: "Science",
        location: "floor c1",
        system: "part time",
        credits: 200,
        level: "grade b",
      },
      {
        id: 4,
        name: "Science II",
        location: "floor c1",
        system: "part time",
        credits: 200,
        level: "grade a",
      },
      {
        id: 5,
        name: "Science III",
        location: "floor c1",
        system: "part time",
        credits: 200,
        level: "grade a",
      },
      {
        id: 6,
        name: "spanish 1",
        location: "floor c1",
        system: "full time",
        credits: 200,
        level: "grade a",
      },
      {
        id: 7,
        name: "spanish 2",
        location: "floor c1",
        system: "part time",
        credits: 200,
        level: "grade a",
      },
      {
        id: 8,
        name: "germany 1",
        location: "floor c1",
        system: "part time",
        credits: 200,
        level: "grade a",
      },
    ];
    locationFilter = LocationFilter.create(students, classes);
  });

  it("filters students when several locations filters are applied together", () => {
    locationFilter.addFilter("floor b1");
    locationFilter.addFilter("floor a1");

    const result = locationFilter.casesFiltered;

    expect(result.length).toBe(2);
    expect(result[1].studentName).toBe("Jhon");
    expect(result[0].studentName).toBe("Juliana");
  });
});

Test aplicando factoría y builders

Extrayendo la lógica para crear objetos de prueba, podemos simplificar el código de nuestros test de forma significativa

import { LocationFilter } from "./original";

// Factoria de clases
function createClass(id: number, location: string): any {
  return {
    id,
    location,
    name: "Irrelevant-Name",
    system: "Irrelevant-System",
    credits: 200,
    level: "Irrelevant-Level",
  };
}

// Factoria de estudiantes
function createStudent(classId: number, studentName: string): any {
  return {
    studentName,
    classId,
    id: "Irrelevant-Id",
    origin: "Irrelevant-origin",
    publicNotes: [],
    privateNotes: [],
  };
}

describe("Location filter", () => {
  it("filters students when several locations filters are applied together", () => {
    let locationFilter: LocationFilter;
    const students = [
      createStudent(1, "Jhon"),
      createStudent(2, "Juliana"),
      createStudent(3, "Irrelevant-student"),
    ];
    const classes = [
      createClass(1, "floor a1"),
      createClass(2, "floor b1"),
      createClass(3, "irrelevant location"),
    ];
    locationFilter = LocationFilter.create(students, classes);
    locationFilter.addFilter("floor b1");
    locationFilter.addFilter("floor a1");

    const result = locationFilter.casesFiltered;

    expect(result.length).toBe(2);
    expect(result[1].studentName).toBe("Jhon");
    expect(result[0].studentName).toBe("Juliana");
  });
});

Puntos importantes de destacar

  • El test y lo que necesita para ejecutarse queda contenido dentro del mismo test y no en bloques beforeEach, esto genera que el test sea completamente independiente

Ejercicio y ejemplo basado en la Kata del curso Testing Sostenible con TypeScript