#angular
#testing
#jasmine
#karma
#unit-test
Day 17 β Unit Testing in Angular: Jasmine, Karma & Spies
π Day 17 β Unit Testing in Angular: Jasmine, Karma & Spies
Zero to Hero β Hands-on Angular Tutorial
Today you will learn:
- βοΈ What is a Unit Test? (
.spec.tsfiles) - βοΈ Jasmine (The testing syntax) & Karma (The runner)
- βοΈ Testing Components (DOM interaction)
- βοΈ Testing Services (Mocking HTTP)
- βοΈ Spies: Fake it til you make it (
spyOn) - βοΈ Handling Signals in tests
Testing allows you to refactor code without fear. π‘οΈ
π¦ 1. Anatomy of a Test File
Every component has a *.component.spec.ts file.
describe('CalculatorComponent', () => { // 1. Suite
it('should add numbers correctly', () => { // 2. Spec (Test Case)
// 3. Expectation (Assertion)
expect(1 + 1).toEqual(2);
expect(true).toBeTrue();
});
});
Run tests with:
ng test
π© 2. Testing a Service (Logic)
Letβs test a simple MathService.
Service:
add(a: number, b: number) { return a + b; }
Test:
import { MathService } from './math.service';
describe('MathService', () => {
let service: MathService;
beforeEach(() => {
service = new MathService();
});
it('should add two numbers', () => {
const result = service.add(2, 3);
expect(result).toBe(5);
});
});
π§ 3. Testing Dependencies (Mocking / Spies)
Services often depend on other services (e.g., HttpClient, Router).
Rule: NEVER call a real API in a unit test. Mock it.
Scenario: AuthService uses HttpClient to login.
import { TestBed } from '@angular/core/testing';
import { AuthService } from './auth.service';
import { HttpClient } from '@angular/common/http';
import { of } from 'rxjs';
describe('AuthService', () => {
let service: AuthService;
let httpSpy: jasmine.SpyObj<HttpClient>;
beforeEach(() => {
// Create a fake HTTP Client
httpSpy = jasmine.createSpyObj('HttpClient', ['post']);
TestBed.configureTestingModule({
providers: [
AuthService,
{ provide: HttpClient, useValue: httpSpy } // π Inject the fake
]
});
service = TestBed.inject(AuthService);
});
it('should return token when login succeeds', (done) => {
const mockResponse = { token: 'FAKE_TOKEN_123' };
// Tell the spy what to return when called
httpSpy.post.and.returnValue(of(mockResponse));
service.login('test@test.com', 'pass').subscribe(res => {
expect(res.token).toBe('FAKE_TOKEN_123');
expect(httpSpy.post).toHaveBeenCalledTimes(1);
done();
});
});
});
- βοΈ
createSpyObj: Creates a fake object with methods. - βοΈ
returnValue: Defines what the fake method returns. - βοΈ
toHaveBeenCalled: Verifies the method was actually used.
π₯ 4. Testing Components (The DOM)
We use ComponentFixture to interact with the HTML.
Component:
// .ts
title = 'Hello World';
// .html
<h1>{{ title }}</h1>
<button (click)="title = 'Clicked!'">Change</button>
Test:
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyComponent } from './my.component';
import { By } from '@angular/platform-browser';
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({ imports: [MyComponent] }).compileComponents();
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
fixture.detectChanges(); // Trigger Initial Render
});
it('should display initial title', () => {
// Query the DOM
const h1 = fixture.nativeElement.querySelector('h1');
expect(h1.textContent).toContain('Hello World');
});
it('should change title on button click', () => {
// 1. Find button
const btn = fixture.debugElement.query(By.css('button'));
// 2. Click it
btn.triggerEventHandler('click', null);
// 3. Update view
fixture.detectChanges();
// 4. Check result
const h1 = fixture.nativeElement.querySelector('h1');
expect(h1.textContent).toContain('Clicked!');
});
});
π« 5. Testing Signals
Signals update instantly, making them easier to test than Observables.
it('should update signal count', () => {
component.increment();
// No need for detectChanges() if checking the class directly
expect(component.count()).toBe(1);
});
it('should reflect signal in DOM', () => {
component.increment();
fixture.detectChanges(); // Sync DOM
const p = fixture.nativeElement.querySelector('p');
expect(p.textContent).toBe('Count: 1');
});
π End of Day 17 β What You Learned
Today you became a responsible developer:
- βοΈ Unit Testing Structure:
describe,it,expect. - βοΈ Spies: Mocking complex dependencies (
HttpClient). - βοΈ DOM Testing: Clicking buttons and checking text in tests.
- βοΈ Signal Testing: Verifying reactive state changes.
π§ͺ Day 17 Challenge
Write tests for your βCounter Componentβ:
Requirements:
- Verify the initial count is 0.
- Find the
+button in the DOM, click it, and verify count is 1. - Find the
-button, click it, and verify count is -1 (or 0 if you have a min limit). - Mock a
LoggerServiceand ensurelogger.log()is called when buttons are clicked.