Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Modern Testing in Angular with Vitest

Modern Testing in Angular with Vitest

Avatar for Fabian Gosebrink

Fabian Gosebrink

October 22, 2026

More Decks by Fabian Gosebrink

Other Decks in Technology

Transcript

  1. Angular Testing Ensure the reliability of your Angular applications with

    robust unit and integration testing strategies using testing tools.
  2. Testing Strategies 1 2 3 4 5 6 7 8

    9 10 11 12 13 14 15 ✓ Easy to test: container @Component({ selector: 'lib-main-dog', templateUrl: './main-dog.component.html', styleUrls: ['./main-dog.component.scss'], providers: [MainDogStore], imports: [DogListComponent, DogRateComponent], }) export class MainDogComponent { dogId = input(''); store = inject(MainDogStore); constructor() { this.store.selectDog(this.dogId); } }
  3. Testing Strategies 1 2 3 4 5 6 7 8

    9 10 ✓ Easy to test: presentational @Component({ selector: 'lib-dog-list', templateUrl: './dog-list.component.html', styleUrls: ['./dog-list.component.scss'], }) export class DogListComponent { dogs = input<Dog[]>([]); dogSelected = output<string>(); }
  4. Testing Strategies ✗ Hard to test: too much logic 1

    @Component({ 2 selector: 'app-products', 3 imports: [ProductCategoryComponent, CategoryNamePipe], 4 templateUrl: './products.component.html', 5 styleUrl: './products.component.scss', 6 }) 7 export class ProductsComponent implements OnInit { 8 products = signal<{ category: string; products: Product[] }[]>([]); 9 private readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); 10 11 private readonly toastrService = inject(ToastrService); 12 private readonly router = inject(Router); 13 ngOnInit() { 14 this.productsService.loadProducts().subscribe((products) => { 15 16 const categoryProductMap = products.reduce( 17 (result: Record<string, Product[]>, product) => { 18 const category = product.category; 19 20 if (!result[category]) {
  5. Testing Strategies ✗ Hard to test: too much logic 1

    @Component({ 2 selector: 'app-products', 3 imports: [ProductCategoryComponent, CategoryNamePipe], 4 templateUrl: './products.component.html', 5 styleUrl: './products.component.scss', 6 }) 7 export class ProductsComponent implements OnInit { 8 products = signal<{ category: string; products: Product[] }[]>([]); 9 private readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); 10 11 private readonly toastrService = inject(ToastrService); 12 private readonly router = inject(Router); 13 14 ngOnInit() { 15 this.productsService.loadProducts().subscribe((products) => { 16 const categoryProductMap = products.reduce( 17 (result: Record<string, Product[]>, product) => { 18 const category = product.category; 19 20 if (!result[category]) {
  6. Testing Strategies ✗ Hard to test: too much logic 1

    @Component({ 14 ngOnInit() { 2 selector: 'app-products', 15 this.productsService.loadProducts().subscribe((products) => { 16 3 imports: CategoryNamePipe], const[ProductCategoryComponent, categoryProductMap = products.reduce( 17 4 templateUrl: './products.component.html', (result: Record<string, Product[]>, product) => { 5 styleUrl: './products.component.scss', 18 const category = product.category; 19 6 }) 20 7 export class ProductsComponent implements OnInit { if (!result[category]) { 21 8 products =result[category] signal<{ category: string; products: Product[] }[]>([]); = []; 22 9 private } readonly productsService = inject(ProductsService); 23 private readonly checkoutService = inject(CheckoutService); 10 24 11 private result[category].push(product); readonly toastrService = inject(ToastrService); 25 12 private readonly router = inject(Router); 26 13 return result; 27 }, 14 ngOnInit() { 28 15 this.productsService.loadProducts().subscribe((products) {}, => { 29 ); 16 const categoryProductMap = products.reduce( 30 17 (result: Record<string, Product[]>, product) => { 31 18 const const groupedByCategory category = product.category; = Object.keys(categoryProductMap).map( 32 19 (category) => ({ 33 20 if (!result[category]) { category,
  7. Testing Strategies 32 1 33 2 34 3 35 4

    36 5 37 6 38 7 39 8 40 9 41 10 42 11 43 12 44 13 45 14 46 15 47 16 48 17 49 18 50 19 51 20 ✗ Hard to test: too much logic (category) => ({ @Component({ category, selector: 'app-products', products: categoryProductMap[category], imports: [ProductCategoryComponent, CategoryNamePipe], }), templateUrl: './products.component.html', ); styleUrl: './products.component.scss', }) this.products.set(groupedByCategory); export class ProductsComponent implements OnInit { }); products = signal<{ category: string; products: Product[] }[]>([]); } private readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); onProductClicked(id: string): void { private readonly toastrService = inject(ToastrService); this.router.navigate(['products', id]); private readonly router = inject(Router); } ngOnInit() { onCartClicked(product: Product): void { this.productsService.loadProducts().subscribe((products) => { this.checkoutService.addToCart(product).subscribe(() => { const categoryProductMap = products.reduce( this.toastrService.success('Item Added to Cart'); (result: Record<string, Product[]>, product) => { }); const category = product.category; } } if (!result[category]) {
  8. Testing Strategies 32 1 33 2 34 3 35 4

    36 5 37 6 38 7 39 8 40 9 41 10 42 11 43 12 44 13 45 14 46 15 47 16 48 17 49 18 50 19 51 20 ✗ Hard to test: too much logic (category) => ({ @Component({ category, selector: 'app-products', products: categoryProductMap[category], imports: [ProductCategoryComponent, CategoryNamePipe], }), templateUrl: './products.component.html', ); styleUrl: './products.component.scss', }) this.products.set(groupedByCategory); export class ProductsComponent implements OnInit { }); products = signal<{ category: string; products: Product[] }[]>([]); } private readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); onProductClicked(id: string): void { private readonly toastrService = inject(ToastrService); this.router.navigate(['products', id]); private readonly router = inject(Router); } ngOnInit() { onCartClicked(product: Product): void { this.productsService.loadProducts().subscribe((products) => { this.checkoutService.addToCart(product).subscribe(() => { const categoryProductMap = products.reduce( this.toastrService.success('Item Added to Cart'); (result: Record<string, Product[]>, product) => { }); const category = product.category; } } if (!result[category]) {
  9. Testing Syntax describe(...) // summarizes multiple tests test(...) // declares

    a test it(...) // declares a test beforeEach(...) // runs before every test afterEach(...) // runs after every test
  10. Testing Syntax describe('1st tests', () => { it('true is true',

    () => { expect(true).toBe(true); }); });
  11. Testing Example - Pipe import { FilterPipe } from './filter.pipe';

    describe('FilterPipe', () => { let filterPipe: FilterPipe; // synchronous beforeEach beforeEach(() => { filterPipe = new FilterPipe(); }); it('filterPipe should be instantiated', () => { // ... }); it('filterPipe should filter', () => { // ... }); });
  12. Testing with Spies // Arrange const mySpy = vi.spyOn(service, 'getAllFood')

    .mockReturnValue(...); // Act comp.updateFood(); // Assert expect(mySpy).toHaveBeenCalledTimes(1);
  13. Testing Example - TestBed beforeEach(async () => { await TestBed.configureTestingModule({

    imports: [HomeComponent], providers: [ { provide: FoodDataService, useClass: FoodServiceMock } ] }).compileComponents(); });
  14. Testing Example - Async 1 it('should load todos', async ()

    => { 2 // arrange 3 const httpClient = TestBed.inject(HttpClient); 4 const service = TestBed.inject(TodoService); 5 const response = [ /* ...*/ ]; 6 const getSpy = vi .spyOn(httpClient, 'get') 7 8 .mockReturnValue(of(response)); 9 10 // act 11 const result = await lastValueFrom(service.loadTodos()); 12 13 // assert 14 expect(getSpy).toHaveBeenCalledOnce(); 15 expect(result).toEqual(response); 16 });