Logo

dev-resources.site

for different kinds of informations.

8.Angular Unit Testing: A Step-by-Step Approach

Published at
12/5/2024
Categories
angular
jasmin
karma
unittest
Author
renukapatil
Categories
4 categories in total
angular
open
jasmin
open
karma
open
unittest
open
Author
11 person written this
renukapatil
open
8.Angular Unit Testing: A Step-by-Step Approach

Before jump on to this make sure you have cleared your basics: Jasmin Testing Framework: A Comprehensive Guide

Unit testing is a crucial aspect of Angular development, ensuring the correctness and reliability of components, services, pipes, and other parts of the application. This guide will walk you through how to set up tests for Angular components and services, along with various techniques like mocking, isolated tests, and using TestBed for dependency injection.

Angular uses Jasmin and karma by default for unit testing.

Image description

1. Service Testing

Angular services contain business logic, and testing them ensures that your core logic functions as expected. Here, we'll go through a basic service example and its tests.

Example: CalculatorService

export class CalculatorService {
  add(n1: number, n2: number): number {
    return n1 + n2;
  }

  subtract(n1: number, n2: number): number {
    return n1 - n2;
  }
}
Enter fullscreen mode Exit fullscreen mode
Test Case:
describe('Calculator Service', () => {
  let calculator: CalculatorService;

  beforeEach(() => {
    calculator = new CalculatorService();
  });

  it('should add two numbers', () => {
    let result = calculator.add(2, 3);
    expect(result).toBe(5);
  });

  it('should subtract two numbers', () => {
    let result = calculator.subtract(5, 3);
    expect(result).toBe(2);
  });
});
Enter fullscreen mode Exit fullscreen mode

2. Testing Services with Dependency Injection

Services can depend on other services via dependency injection. Let's explore a scenario where CalculatorService depends on a LoggerService to log actions.

Example: LoggerService

export class LoggerService {
  msg: string[] = [];

  log(message: string): void {
    this.msg.push(message);
  }
}
Enter fullscreen mode Exit fullscreen mode

Updated CalculatorService with LoggerService:

export class CalculatorService {
  constructor(private loggerService: LoggerService) {}

  add(n1: number, n2: number): number {
    let result = n1 + n2;
    this.loggerService.log('Addition performed');
    return result;
  }

  subtract(n1: number, n2: number): number {
    let result = n1 - n2;
    this.loggerService.log('Subtraction performed');
    return result;
  }
}
Enter fullscreen mode Exit fullscreen mode

Test Case with Mocking the LoggerService:

describe('Calculator Service with Logger', () => {
  let calculator: CalculatorService;
  let mockLoggerService: jasmine.SpyObj<LoggerService>;

  beforeEach(() => {
    mockLoggerService = jasmine.createSpyObj('LoggerService', ['log']);
    calculator = new CalculatorService(mockLoggerService);
  });

  it('should add two numbers and log the operation', () => {
    let result = calculator.add(2, 3);
    expect(result).toBe(5);
    expect(mockLoggerService.log).toHaveBeenCalledWith('Addition performed');
  });

  it('should subtract two numbers and log the operation', () => {
    let result = calculator.subtract(5, 3);
    expect(result).toBe(2);
    expect(mockLoggerService.log).toHaveBeenCalledWith('Subtraction performed');
  });
});
Enter fullscreen mode Exit fullscreen mode

3. Types of Mocking

Mocking helps isolate tests and avoid calling real services. There are three types of mocking in unit tests:

  1. Stub: Provides hardcoded responses to method calls.
  2. Dummy: Used to fill method parameters without returning meaningful data.
  3. Spy: Records the calls made to a method, useful for verifying interactions.

Using Jasmineโ€™s createSpyObj for Spies:

let mockLoggerService = jasmine.createSpyObj('LoggerService', ['log']);
Enter fullscreen mode Exit fullscreen mode

This spy can track how many times the log method is called and with which arguments.


4. Isolated Test Case

An isolated test case tests a single unit in isolation without its dependencies. For example, testing CalculatorService without involving LoggerService:

describe('CalculatorService (Isolated Test)', () => {
  let calculator: CalculatorService;

  beforeEach(() => {
    calculator = new CalculatorService();
  });

  it('should add numbers correctly', () => {
    expect(calculator.add(2, 3)).toBe(5);
  });

  it('should subtract numbers correctly', () => {
    expect(calculator.subtract(5, 3)).toBe(2);
  });
});
Enter fullscreen mode Exit fullscreen mode

5. Using beforeEach for Setup

To avoid repetitive setup in each test, we can use beforeEach to initialize components or services before each test case.

describe('CalculatorService', () => {
  let calculator: CalculatorService;

  beforeEach(() => {
    calculator = new CalculatorService();
  });

  it('should add two numbers', () => {
    expect(calculator.add(2, 3)).toBe(5);
  });

  it('should subtract two numbers', () => {
    expect(calculator.subtract(5, 3)).toBe(2);
  });
});
Enter fullscreen mode Exit fullscreen mode

6. Testing Pipes

Pipes in Angular transform data. Let's test a custom pipe that converts numbers into strength descriptions.

Example Pipe:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({name: 'strength'})
export class StrengthPipe implements PipeTransform {
  transform(value: number): string {
    if (value < 10) {
      return `${value} (weak)`;
    } else if (value < 20) {
      return `${value} (strong)`;
    } else {
      return `${value} (strongest)`;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Test Case for Pipe:

describe('StrengthPipe', () => {
  let pipe: StrengthPipe;

  beforeEach(() => {
    pipe = new StrengthPipe();
  });

  it('should transform number to "weak" for values less than 10', () => {
    expect(pipe.transform(5)).toBe('5 (weak)');
  });

  it('should transform number to "strong" for values between 10 and 20', () => {
    expect(pipe.transform(15)).toBe('15 (strong)');
  });

  it('should transform number to "strongest" for values greater than 20', () => {
    expect(pipe.transform(25)).toBe('25 (strongest)');
  });
});
Enter fullscreen mode Exit fullscreen mode

7. Component Testing with Input and Output

When testing components that interact with user input or emit events, we need to handle the @Input and @Output properties.

Component with @Input and @Output:

@Component({
  selector: 'app-post',
  template: `
    <div>
      <p>{{ post.title }}</p>
      <button (click)="onDelete()">Delete</button>
    </div>
  `
})
export class PostComponent {
  @Input() post!: Post;
  @Output() delete = new EventEmitter<Post>();

  onDelete() {
    this.delete.emit(this.post);
  }
}
Enter fullscreen mode Exit fullscreen mode

Test Case for Component's Output:

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

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [PostComponent],
    });

    fixture = TestBed.createComponent(PostComponent);
    component = fixture.componentInstance;
  });

  it('should raise an event when delete is clicked', () => {
    const post: Post = { id: 1, title: 'Post 1', body: 'Body 1' };
    component.post = post;

    spyOn(component.delete, 'emit');

    component.onDelete();

    expect(component.delete.emit).toHaveBeenCalledWith(post);
  });
});
Enter fullscreen mode Exit fullscreen mode

8. Using TestBed to Test Components

TestBed helps to set up the testing environment by creating the Angular testing module and resolving dependencies.

Component Setup Using TestBed:

describe('PostComponent with TestBed', () => {
  let fixture: ComponentFixture<PostComponent>;
  let component: PostComponent;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [PostComponent],
    });

    fixture = TestBed.createComponent(PostComponent);
    component = fixture.componentInstance;
  });

  it('should create the component', () => {
    expect(component).toBeTruthy();
  });
});
Enter fullscreen mode Exit fullscreen mode

Accessing Template Elements:

To test template bindings or DOM manipulations, we use nativeElement or debugElement.

it('should display the post title', () => {
  const bannerElement: HTMLElement = fixture.nativeElement;
  const p = bannerElement.querySelector('p')!;
  expect(p.textContent).toEqual('Post 1');
});
Enter fullscreen mode Exit fullscreen mode

Summary

  1. Introduction to Jasmine & Karma: Jasmine is a behavior-driven development (BDD) framework for testing JavaScript, while Karma is a test runner that executes tests in real browsers. They work together to simplify testing in Angular applications.

  2. Testing Services: Unit testing services is explained with an emphasis on using Jasmineโ€™s SpyObj to mock service dependencies, ensuring isolation in tests.

  3. Component Testing: Testing components involves simulating input and output interactions and using TestBed to create a testing module.

  4. Mocking & Spying: Mocking methods with spies allows you to track function calls and control method behavior in isolated tests.

Unit testing in Angular can seem overwhelming at first, but with practice, you will learn how to write tests that are clear, isolated, and maintainable. By using techniques like mocking, beforeEach, and TestBed, you can create robust tests that ensure your Angular applications behave as expected.

unittest Article's
30 articles in total
Favicon
Getting Started with Android Testing: Building Reliable Apps with Confidence (Part 1/3)
Favicon
What is the Order of Importance for Unit, Integration, and Acceptance Tests in Software Development?
Favicon
Top 17 Best Unit Testing Tools
Favicon
JUnit Testing: A Comprehensive Guide to Unit Testing in Java
Favicon
Crafting Effective Unit Tests for Generative AI Applications
Favicon
Harder, Better, Faster, Stronger Tests With Fixtures
Favicon
How To Test Your JavaScript Application With Jest Framework?
Favicon
Effective Strategies for Writing Unit Tests with External Dependencies like Databases and APIs
Favicon
Debugging Laravel Routes in Testing
Favicon
Is Unit Test really a MUST?
Favicon
Let's Learn Unit Testing in Python with pytest! ๐Ÿš€
Favicon
An opinionated testing approach for VueJS
Favicon
PHP: Should I mock or should I go?
Favicon
Implementing Unit Testing in ReadmeGenie
Favicon
8.Angular Unit Testing: A Step-by-Step Approach
Favicon
โ€œWhy Unit Testing Is Not an Option, But a Dutyโ€
Favicon
Early Raises $5M to Transform Software Development
Favicon
Smth about Unittests
Favicon
Unit Testing in Laravel: A Practical Approach for Developers
Favicon
Assert with Grace: Custom Soft Assertions using AssertJ for Cleaner Code
Favicon
Writing Integration And Unit Tests for a Simple Fast API application using Pytest
Favicon
End-To-End Testing for Java+React Applications
Favicon
Writing XUnit Tests without Object Arrays
Favicon
Comprehensive Testing in .NET 8: Using Moq and In-Memory Databases
Favicon
Test-Driven Development (TDD) in Front-end.
Favicon
Unit testing
Favicon
jUnit - Testes unitรกrios em Java
Favicon
Testing Spring Boot Applications: Unit, Integration, and Mocking โ€” A Comprehensive Guide
Favicon
Letโ€™s Make Jest Run Much Faster
Favicon
Test-Driven Development

Featured ones: