Share
Embed
Transcript
34 slides
-
Angular Testing Ensure the reliability of your Angular applications with
robust unit and integration testing strategies using testing tools. -
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); } } -
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>(); } -
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]) { -
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]) { -
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, -
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]) { -
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]) { -
Testing Syntax describe(...) // summarizes multiple tests test(...) // declares
a test it(...) // declares a test beforeEach(...) // runs before every test afterEach(...) // runs after every test -
Testing Syntax describe('1st tests', () => { it('true is true',
() => { expect(true).toBe(true); }); }); -
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', () => { // ... }); }); -
Testing Assertions expect(…).toBe(…); // Assert // Matcher toBe toBeDefined toBeTruthy
toEqual toMatch toContain toThrow ... -
Testing with Spies // Arrange const mySpy = vi.spyOn(service, 'getAllFood')
.mockReturnValue(...); // Act comp.updateFood(); // Assert expect(mySpy).toHaveBeenCalledTimes(1); -
Testing Example - TestBed beforeEach(async () => { await TestBed.configureTestingModule({
imports: [HomeComponent], providers: [ { provide: FoodDataService, useClass: FoodServiceMock } ] }).compileComponents(); }); -
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 });