A. Introduction: What is Testing?

Software testing is the process of verifying and validating that an application functions as intended and meets its specified requirements. It plays a critical role in the software development lifecycle by identifying defects, ensuring application reliability, and maintaining code quality before software reaches end users.

As applications grow in complexity, even small code changes can introduce unexpected issues that impact existing functionality. Testing helps developers detect these problems early, reduce the risk of regressions, and ensure that new features integrate seamlessly with the existing codebase. It also provides the confidence to refactor, optimize, and enhance applications without compromising stability.

Modern software development relies on various types of testing, each designed to validate a different aspect of an application. From testing individual functions to verifying complete user workflows, an effective testing strategy helps teams deliver secure, scalable, and high-quality software.

B. Types of Testing

Software testing consists of various testing approaches, each serving a specific purpose in ensuring the quality, reliability, and performance of an application. Choosing the right type of testing depends on what you want to validate—whether it’s a single function, multiple integrated components, or the complete application from an end user’s perspective.

1. Unit Testing

Unit testing focuses on testing individual functions, methods, or components in isolation. The goal is to verify that each unit of code behaves as expected without depending on external systems such as databases or APIs. It is typically written by developers during the development process.

Example:

  • Testing a FastAPI service function.
  • Testing an Angular component method.

2. Integration Testing

Integration testing verifies that multiple modules or components work correctly when combined. It ensures that interactions between services, databases, APIs, and other dependencies function as expected.

Example:

  • Testing a FastAPI API endpoint with a test database.
  • Verifying communication between Angular services and backend APIs.

3. Functional Testing

Functional testing validates that an application’s features work according to the specified business requirements. It focuses on the expected behavior rather than the internal implementation.

Example:

  • Verifying that a user can successfully log in.
  • Checking that an employee can be created through the application.

4. API Testing

API testing evaluates the functionality, reliability, performance, and security of application programming interfaces (APIs). It ensures that endpoints return the correct responses, status codes, and handle invalid requests appropriately.

Example:

  • Testing a FastAPI endpoint using different request payloads.
  • Verifying authentication and authorization mechanisms.

5. UI (User Interface) Testing

UI testing ensures that the application’s user interface behaves correctly and provides a consistent user experience. It validates visual elements, user interactions, and interface responsiveness.

Example:

  • Testing button clicks.
  • Verifying form validation messages.

6. End-to-End (E2E) Testing

End-to-End testing simulates real user scenarios by testing the complete application workflow from start to finish. It verifies that all integrated components work together seamlessly.

Example:

A user:

  • Logs in
  • Creates an employee
  • Updates employee details
  • Deletes the employee
  • Logs out

This entire workflow is validated as a single test.

7. Performance Testing

Performance testing measures how an application performs under different workloads. It helps identify bottlenecks, response times, scalability issues, and resource utilization.

Example:

  • Simulating thousands of API requests simultaneously.
  • Measuring API response time under heavy traffic.

8. Security Testing

Security testing identifies vulnerabilities and ensures that an application is protected against unauthorized access, data breaches, and common cyber threats.

Example:

  • Testing JWT authentication.
  • Verifying role-based access control.
  • Checking protection against SQL Injection and Cross-Site Scripting (XSS).

The Test Pyramid

Unit Tests → Integration Tests → E2E Tests

              ^
E2E Tests 
/----------------\ 
/Integration Tests\ 
/------------------------\ 
/          Unit Tests          \ 
/______________________________\

Unit tests verify individual functions, methods, or components in isolation. Integration tests verify that multiple parts of the application work together, such as an API endpoint with its application dependencies. E2E tests validate complete user workflows from the UI through the backend and other integrated systems.

Backend Unit Testing Architecture (FastAPI)

ash-mgmt-api/
│
├── src/
│   ├── modules/
│   │   ├── manage_users/
│   │   │   ├── crud.py
│   │   │   ├── router.py
│   │   │   ├── schema.py
│   │   │   └── __init__.py
│   │   │
│   │   ├── another_module/
│   │   │   ├── crud.py
│   │   │   ├── router.py
│   │   │   ├── schema.py
│   │   │   └── __init__.py
│   │   │
│   │   └── ...
│   │
│   └── main.py
│
├── tests/
│   ├── conftest.py
│   │
│   ├── modules/
│   │   ├── manage_users/
│   │   │   ├── test_crud.py
│   │   │   ├── test_router.py
│   │   │   ├── test_schema.py
│   │   │   └── __init__.py
│   │   │
│   │   ├── another_module/
│   │   │   ├── test_crud.py
│   │   │   ├── test_router.py
│   │   │   ├── test_schema.py
│   │   │   └── __init__.py
│   │   │
│   │   └── ...
│   │
│   └── __init__.py
│
├── requirements.txt
├── pytest.ini
└── README.md

Run Test File

> pytest
 or
> pytest -v

Run a Specific Test File

> pytest tests/modules/manage_users/test_crud.py

Where to Find Backend Test File

project_name/htmlcov/index.html

Frontend Unit Testing Architecture (Angular)

ash-mgmt-frontend/
│
├── src/
│   ├── app/
│   │
│   ├── core/
│   │   ├── services/
│   │   │   ├── auth.service.ts
│   │   │   ├── auth.service.spec.ts
│   │   │   └── ...
│   │   │
│   │   └── guards/
│   │       ├── auth.guard.ts
│   │       ├── auth.guard.spec.ts
│   │       └── ...
│   │
│   ├── pages/
│   │   ├── dashboard/
│   │   │   ├── dashboard.component.ts
│   │   │   ├── dashboard.component.spec.ts
│   │   │   └── ...
│   │   │
│   │   └── manage-users/
│   │       ├── manage-users.component.ts
│   │       ├── manage-users.component.spec.ts
│   │       └── ...
│   │
│   ├── app.component.ts
│   ├── app.component.spec.ts
│   └── main.ts
│
├── angular.json
├── package.json
└── karma.conf.js

karma.conf.js

The karma.conf.js file is the configuration file for Karma, Angular’s default test runner. It defines how unit tests are executed, which browser to use, which files to include, reporting options, and code coverage settings.

Understanding Code Coverage Metrics

Code coverage measures which parts of the application were executed while the test suite was running. It is useful for finding untested areas, but high code coverage does not guarantee that the application is bug-free. Tests still need meaningful assertions and realistic scenarios.

  • Statements: The percentage of executable statements that were executed by the tests.
  • Branches: The percentage of decision paths that were executed, such as both sides of if/else conditions.
  • Functions: The percentage of functions or methods that were called by the tests.
  • Lines: The percentage of executable source-code lines that were executed.

Aim for meaningful coverage of critical business logic rather than simply chasing 100% coverage.

Run Test File

> ng test

Run a Specific Test File

> ng test --include=”src/pages/tender/manage-tender/manage-tender.spec.ts”

Run a Specific Test File with Coverage Report

> ng test --include=”src/pages/tender/manage-tender/manage-tender.spec.ts” --code-coverage

Where to Find Frontend Test File

project_name/coverage/xtreme/index.html

Creating a Bridge Between Dependent Modules

In a real-world Angular application, a component rarely works independently. It usually depends on multiple services, modules, routers, or other components to perform its functionality.

For example, a ManageTenderComponent may depend on a TenderService to fetch tender data:

ManageTenderComponent
        ↓
TenderService
        ↓
API / Backend

While writing unit tests, calling the actual API or using the complete implementation of every dependency is generally not recommended. Instead, we create a bridge between the component under test and its dependent modules/services using Angular’s TestBed and Dependency Injection.

The bridge allows us to replace real dependencies with mocks, stubs, or spies.

Unit Test
      ↓
TestBed
↓
ManageTenderComponent
      ↓
Mock TenderService
↓
Mock Response

Why Is This Bridge Important?

Creating a bridge between dependent modules or services provides several benefits:

  • Isolation: Tests focus only on the functionality being tested.
  • Faster execution: Real API calls and unnecessary dependencies are avoided.
  • Controlled data: We can provide specific responses for different test scenarios.
  • Reliable tests: Tests do not depend on external services or backend availability.
  • Easy error testing: We can easily simulate API errors, empty responses, or successful responses.
  • Better debugging: If a test fails, it is easier to identify the actual source of the problem.

Example

Suppose ManageTenderComponent depends on TenderService:

constructor(private tenderService: TenderService) {}

Instead of providing the actual TenderService, we can create a mock service:

const tenderServiceMock = {
  getTenders: jasmine.createSpy('getTenders')
    .and.returnValue(of([]))
};

Then configure the mock service in TestBed:

TestBed.configureTestingModule({
  providers: [
    {
      provide: TenderService,
      useValue: tenderServiceMock
    }
  ]
});

Now, whenever ManageTenderComponent calls TenderService, the test uses the mocked implementation instead of the real service.

For example:

it('should load tenders', () => {
  const mockTenders = [
    { id: 1, name: 'Tender 1' }
  ];
  tenderServiceMock.getTenders.and.returnValue(of(mockTenders));
  component.loadTenders();
  expect(tenderServiceMock.getTenders).toHaveBeenCalled();
  expect(component.tenders).toEqual(mockTenders);
});

This creates a clear bridge:

ManageTenderComponent
        ↓
TenderService
        ↓
tenderServiceMock
        ↓
Mock Tender Data

This approach is especially useful when a component has multiple dependencies. Each dependency can be replaced with an appropriate mock or spy, allowing the unit test to remain focused, independent, and predictable.

Key Concept

The main purpose of creating this bridge is not to test all dependent modules at the same time. The goal of a unit test is to test one unit in isolation while controlling the behavior of its dependencies.

For Angular applications, TestBed and Dependency Injection provide the mechanism to create this controlled testing environment.

Unit Testing Best Practices

Writing effective unit tests is as important as writing the application code itself. The following practices help keep a test suite reliable, fast, and easy to maintain as the codebase grows.

  • Follow the Arrange-Act-Assert (AAA) pattern to keep each test structured and easy to read.
  • Test one behavior per test case and use descriptive, intention-revealing test names.
  • Mock or stub external dependencies such as databases, APIs, and third-party services to keep tests fast and isolated.
  • Keep tests independent of one another so they can run in any order without side effects.
  • Test the public behavior and output of a unit rather than its internal implementation details.
  • Aim for meaningful code coverage on critical business logic rather than chasing 100% coverage.
  • Run unit tests automatically as part of the CI/CD pipeline before code is merged.
  • Keep tests fast so they can be run frequently during development without slowing the team down.

Unit Test Example (FastAPI)

The example below shows a simple unit test for a FastAPI service function, using pytest and unittest.mock to isolate the function from its database dependency.

# tests/modules/manage_users/test_crud.py
from unittest.mock import MagicMock
from src.modules.manage_users import crud

def test_get_user_by_id_returns_user():
    mock_db = MagicMock()
    mock_db.query().filter().first.return_value = {"id": 1, "name": "Krishna"}
    result = crud.get_user_by_id(mock_db, user_id=1)
    assert result["id"] == 1
    assert result["name"] == "Krishna"

Unit Test Example (Angular)

The example below shows a unit test for an Angular component method, using Jasmine and TestBed to mock the service dependency.

// manage-users.component.spec.ts
describe('ManageUsersComponent', () => {
  let component: ManageUsersComponent;
  let authServiceSpy: jasmine.SpyObj<AuthService>;

  beforeEach(() => {
    authServiceSpy = jasmine.createSpyObj('AuthService', ['getUser']);
    TestBed.configureTestingModule({
      declarations: [ManageUsersComponent],
      providers: [{ provide: AuthService, useValue: authServiceSpy }]
    });
    component = TestBed.createComponent(ManageUsersComponent).componentInstance;
  });

  it('should load the current user', () => {
    authServiceSpy.getUser.and.returnValue({ id: 1, name: 'Krishna' });
    component.loadUser();
    expect(component.user.name).toBe('Krishna');
  });
});

What is a good code coverage percentage?

There is no universal code coverage percentage that guarantees software quality. As a practical guideline, 70–80% coverage is generally considered acceptable, 80–90% is a good target, and 90%+ is excellent. However, teams should focus on meaningful tests for critical business logic rather than simply trying to achieve 100% coverage.

Branch coverage is especially important because high statement coverage does not necessarily mean that all conditional paths have been tested.

Important: High code coverage does not guarantee a bug-free application. A test suite can achieve high coverage while still missing important business scenarios or containing weak assertions.

Conclusion: Testing in FastAPI & Angular

Testing is a fundamental part of building reliable, maintainable software. Unit testing in particular gives developers fast feedback and the confidence to change code without breaking existing functionality. Combined with integration, functional, API, UI, end-to-end, performance, and security testing, teams can catch issues early and ship high-quality applications.

In FastAPI, tools such as pytest, TestClient, and unittest.mock make it straightforward to test service functions and API endpoints in isolation. In Angular, Karma and Jasmine provide a built-in framework for testing components, services, and guards through .spec.ts files, with code coverage reports to track how much of the codebase is verified.

Adopting good testing habits early – writing small, focused tests, mocking external dependencies, and running tests automatically in CI/CD – pays off as an application grows, making it easier to refactor, scale, and maintain the codebase with confidence.