Testing Practices

This document outlines the testing practices and procedures for the Digibit ERP system.

Testing Strategy

Test Pyramid

Our testing strategy follows the test pyramid principle: - Unit Tests: 70% of tests - Test individual units of code in isolation - Integration Tests: 20% of tests - Test interactions between components/modules - End-to-End Tests: 10% of tests - Test complete user workflows

Unit Testing

Framework

  • Use Jasmine as the testing framework
  • Use Karma as the test runner
  • Leverage Angular Testing Utilities

Writing Unit Tests

Component Tests

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CompanyDetailComponent } from './company-detail.component';
import { CompanyService } from '../services/company.service';
import { of } from 'rxjs';

describe('CompanyDetailComponent', () => {
  let component: CompanyDetailComponent;
  let fixture: ComponentFixture<CompanyDetailComponent>;
  let companyService: jasmine.SpyObj<CompanyService>;

  beforeEach(async () => {
    const spy = jasmine.createSpyObj('CompanyService', ['getCompany']);

    await TestBed.configureTestingModule({
      declarations: [CompanyDetailComponent],
      providers: [
        { provide: CompanyService, useValue: spy }
      ]
    }).compileComponents();

    companyService = TestBed.inject(CompanyService) as jasmine.SpyObj<CompanyService>;
  });

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

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should load company on initialization', () => {
    const mockCompany = { id: '1', name: 'Test Company' };
    companyService.getCompany.and.returnValue(of(mockCompany));

    component.companyId = '1';
    component.ngOnInit();

    expect(companyService.getCompany).toHaveBeenCalledWith('1');
    expect(component.company).toEqual(mockCompany);
  });
});

Service Tests

import { TestBed } from '@angular/core/testing';
import { CompanyService } from './company.service';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

describe('CompanyService', () => {
  let service: CompanyService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [CompanyService]
    });
    service = TestBed.inject(CompanyService);
    httpMock = TestBed.inject(HttpTestingController);
  });

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

  it('should retrieve companies', () => {
    const mockCompanies = [
      { id: '1', name: 'Company 1' },
      { id: '2', name: 'Company 2' }
    ];

    service.getCompanies().subscribe(companies => {
      expect(companies.length).toBe(2);
      expect(companies).toEqual(mockCompanies);
    });

    const req = httpMock.expectOne('api/companies');
    expect(req.request.method).toBe('GET');
    req.flush(mockCompanies);
  });
});

Test Coverage

  • Aim for at least 80% code coverage
  • Focus on testing business logic rather than simple getter/setter methods
  • Use Istanbul for coverage reports
  • Monitor coverage trends over time

Integration Testing

Component Integration

  • Test how components interact with each other
  • Verify data flow between parent and child components
  • Test directive and pipe functionality with real data

Service Integration

  • Test services with actual HTTP requests (when appropriate)
  • Verify data transformations and business logic
  • Test error handling in real scenarios

End-to-End Testing

Framework

  • Use Cypress for end-to-end testing
  • Test critical user journeys
  • Simulate real user interactions

Example E2E Test

describe('Company Management', () => {
  beforeEach(() => {
    cy.visit('/companies');
    cy.login(); // Custom command for authentication
  });

  it('should allow creating a new company', () => {
    cy.get('[data-testid="add-company-btn"]').click();

    cy.get('[data-testid="company-name-input"]').type('New Company');
    cy.get('[data-testid="company-description-input"]').type('Description for new company');

    cy.get('[data-testid="save-company-btn"]').click();

    cy.get('[data-testid="company-list"]')
      .contains('New Company')
      .should('be.visible');
  });

  it('should display companies in the list', () => {
    cy.get('[data-testid="company-list-item"]').should('have.length.greaterThan', 0);
  });
});

E2E Best Practices

  • Test user workflows rather than individual components
  • Use data-testid attributes for element selection
  • Keep tests independent and deterministic
  • Use fixtures for test data
  • Mock external services that are not under test

Testing Guidelines

Test Naming Convention

  • Use the format: [Scenario] should [Expected Result] when [Condition]
  • Example: should display error message when username is empty

Arrange-Act-Assert Pattern

Structure tests using the AAA pattern: 1. Arrange: Set up test data and conditions 2. Act: Execute the functionality being tested 3. Assert: Verify the expected outcome

Mocking Dependencies

  • Use mocks for external services and APIs
  • Mock complex dependencies to isolate the unit under test
  • Use fake data that represents realistic scenarios

Async Testing

  • Handle asynchronous operations properly
  • Use async/await or promises as appropriate
  • Wait for operations to complete before assertions

Continuous Testing

Pre-commit Hooks

  • Run unit tests before committing code
  • Fail fast if tests don't pass
  • Use lint-staged to run tests only on changed files

CI Pipeline

  • Execute tests on every push to the repository
  • Run different test suites in parallel when possible
  • Generate coverage reports and publish them
  • Fail the build if tests fail or coverage drops below threshold

Quality Metrics

Test Effectiveness

  • Track test pass/fail rates
  • Monitor flaky tests and address them promptly
  • Measure mean time to detect failures
  • Evaluate test execution time and optimize slow tests

Code Quality

  • Integrate static analysis tools
  • Check for code complexity and maintainability
  • Identify and eliminate duplicate code
  • Ensure consistent code style across the project