angular
July 8

Паттерн "Построение - Операции - Проверка"

Паттерн "Построение - Операции - Проверка" (Arrange - Act - Assert, AAA) широко используется при написании тестов для организации тестового кода и повышения его читаемости. Этот паттерн делит тест на три четко определенные секции:

  1. Построение (Arrange): Подготовка всего необходимого для теста, например, создание объектов, настройка условий.
  2. Операции (Act): Выполнение действий, которые нужно протестировать.
  3. Проверка (Assert): Проверка, что результаты выполнения действий соответствуют ожидаемым значениям.

Рассмотрим пример использования этого паттерна при написании теста для Angular компонента.

Предположим, у нас есть простой Angular компонент, который выполняет некую операцию. Например, компонент CalculatorComponent, который имеет метод для сложения двух чисел.

// calculator.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-calculator',
  template: `
    <div>
      <input [(ngModel)]="a" type="number" placeholder="Enter first number">
      <input [(ngModel)]="b" type="number" placeholder="Enter second number">
      <button (click)="add()">Add</button>
      <p>Result: {{ result }}</p>
    </div>
  `
})
export class CalculatorComponent {
  a: number = 0;
  b: number = 0;
  result: number = 0;

  add() {
    this.result = this.a + this.b;
  }
}


Тест

Теперь напишем тест для компонента CalculatorComponent, который проверяет корректность работы метода add.

// calculator.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { CalculatorComponent } from './calculator.component';

describe('CalculatorComponent', () => {
  let component: CalculatorComponent;
  let fixture: ComponentFixture<CalculatorComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ CalculatorComponent ],
      imports: [ FormsModule ]
    })
    .compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(CalculatorComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should add two numbers correctly', () => {
    // Arrange
    component.a = 5;
    component.b = 3;

    // Act
    component.add();

    // Assert
    expect(component.result).toBe(8);
  });
});

Пояснение к тесту

  1. Построение (Arrange):
    • Создаем экземпляр компонента CalculatorComponent с помощью TestBed.
    • Устанавливаем значения для свойств a и b.
  2. Операции (Act):
    • Вызываем метод add() компонента, который выполняет сложение двух чисел.
  3. Проверка (Assert):
    • Проверяем, что результат сложения (component.result) соответствует ожидаемому значению 8.

Рассмотрим другой пример, на этот раз с компонентом, который делает HTTP-запрос для получения данных. Мы напишем тест для проверки корректности обработки этих данных.

Создание Angular компонента с HTTP-запросом

Предположим, у нас есть компонент UserComponent, который получает список пользователей с сервера.

// user.component.ts
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

interface User {
  id: number;
  name: string;
}

@Component({
  selector: 'app-user',
  template: `
    <div *ngIf="users">
      <ul>
        <li *ngFor="let user of users">{{ user.name }}</li>
      </ul>
    </div>
  `
})
export class UserComponent implements OnInit {
  users: User[] | undefined;

  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.getUsers().subscribe(users => this.users = users);
  }

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>('https://jsonplaceholder.typicode.com/users');
  }
}


Тест

Теперь напишем тест для компонента UserComponent, который проверяет корректность получения и отображения списка пользователей.

// user.component.spec.ts
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { UserComponent } from './user.component';
import { HttpClient } from '@angular/common/http';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';

describe('UserComponent', () => {
  let component: UserComponent;
  let fixture: ComponentFixture<UserComponent>;
  let httpMock: HttpTestingController;
  let httpClient: HttpClient;
  let debugElement: DebugElement;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ UserComponent ],
      imports: [ HttpClientTestingModule ]
    })
    .compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(UserComponent);
    component = fixture.componentInstance;
    httpMock = TestBed.inject(HttpTestingController);
    httpClient = TestBed.inject(HttpClient);
    debugElement = fixture.debugElement;
    fixture.detectChanges();
  });

  it('should fetch and display a list of users', () => {
    // Arrange
    const mockUsers = [
      { id: 1, name: 'John Doe' },
      { id: 2, name: 'Jane Doe' }
    ];

    // Act
    const request = httpMock.expectOne('https://jsonplaceholder.typicode.com/users');
    request.flush(mockUsers);
    fixture.detectChanges();

    // Assert
    const userElements = debugElement.queryAll(By.css('li'));
    expect(userElements.length).toBe(2);
    expect(userElements[0].nativeElement.textContent).toContain('John Doe');
    expect(userElements[1].nativeElement.textContent).toContain('Jane Doe');
  });

  afterEach(() => {
    httpMock.verify();
  });
});

Пояснение к тесту

  1. Построение (Arrange):
    • Создаем экземпляр компонента UserComponent с помощью TestBed.
    • Устанавливаем HttpClientTestingModule для подмены HTTP-запросов.
    • Определяем макет данных (mockUsers), который будет возвращен при HTTP-запросе.
  2. Операции (Act):
    • Запускаем HTTP-запрос и подменяем ответ с помощью метода flush.
    • Вызываем fixture.detectChanges() для обновления шаблона компонента.
  3. Проверка (Assert):
    • Проверяем, что список пользователей отображается корректно, сравнивая количество и содержимое элементов списка (<li>).

Примеры демонстрируют, как можно использовать паттерн "Построение - Операции - Проверка" для написания тестов. Такой подход помогает структурировать тесты, делая их более понятными и легкими в поддержке.