Intro
This time, I tested a component of Angular.[Angular]Test with Azure DevOps
Sample
Test target was here.first-page.component.html
<p>first-page works!</p>
<div id="product-list" *ngFor="let p of products">
<div class="product-id">ID: {{p.id}}</div>
<div class="product-name">NAME: {{p.name}}</div>
</div>
<button id="load-button" (click)="loadProducts()"></button>
<input id="input-file" type="file" (change)="fileChanged($event.target)" >
<button id="save-button" (click)="saveFile()">Save</button>
first-page.component.ts
import { Component, OnInit } from '@angular/core';
import { FirstPageService } from './first-page.service';
import { Product } from '../products/product';
import { FileUploadService } from '../file-upload/file-upload.service';
@Component({
selector: 'app-first-page',
templateUrl: './first-page.component.html',
styleUrls: ['./first-page.component.css']
})
export class FirstPageComponent implements OnInit {
public products: Product[] = [];
private uploadFile: File;
constructor(private firstPageService: FirstPageService,
private fileUploaderService: FileUploadService) { }
ngOnInit() {
}
public loadProducts() {
this.firstPageService
.getProducts()
.subscribe(p => {
this.products = [];
this.products.push(...p);
});
}
public fileChanged(target: EventTarget) {
const fileElement = target as HTMLInputElement;
if (fileElement == null ||
fileElement.files == null ||
fileElement.files.length <= 0) {
return;
}
// the first file is set as upload target.
this.uploadFile = fileElement.files[0];
}
public saveFile() {
if (window.confirm('Upload file?') === false) {
return;
}
if (this.uploadFile == null) {
return;
}
this.fileUploaderService.upload(this.uploadFile)
.subscribe(result => console.log(result), // onNext
error => console.error(error), // onError
() => alert('finished')); // onComplete
}
}
product.ts
export type Product = {
id: number,
name: string,
};
upload-result.ts
export type UploadResult = {
succeeded: boolean,
errorMessage: string
};
This component had 2 functions.
- Getting "Product" list and putting into DOM
- Uploading files after confirming
- FirstPageService: Getting "Product" list
- FileUploadService: Uploading files
Stub
First, I added stubs of the services to resolve dependencies and return dummy data.first-page.component.spec.ts
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FirstPageComponent } from './first-page.component';
import { FirstPageService } from './first-page.service';
import { of } from 'rxjs';
import { By } from '@angular/platform-browser';
import { FileUploadService } from '../file-upload/file-upload.service';
describe('FirstPageComponent', () => {
let component: FirstPageComponent;
let fixture: ComponentFixture<FirstPageComponent>;
let firstPageService: FirstPageService;
let fileUploadService: FileUploadService;
beforeEach(async(() => {
const firstPageServiceStub: Partial<FirstPageService> = {
getProducts: () => {
return of([
{ id: 1, name: 'hello' }
]);
}
};
const fileUploadServiceStub: Partial<FileUploadService> = {
upload: (file) => of({ succeeded: true, errorMessage: '' })
};
TestBed.configureTestingModule({
declarations: [ FirstPageComponent ],
providers: [{ provide: FirstPageService, useValue: firstPageServiceStub },
{ provide: FileUploadService, useValue: fileUploadServiceStub }],
})
.compileComponents();
}));
beforeEach(() => {
// firstPageServiceStub was set
firstPageService = TestBed.get(FirstPageService);
// print [{ id: 1, name: 'hello' }]
firstPageService.getProducts()
.subscript(p => console.log(p));
// fileUploadServiceStub was set
fileUploadService = TestBed.get(FileUploadService);
// firstPageServiceStub and fileUploadServiceStub were set as the services
fixture = TestBed.createComponent(FirstPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
});
When I needed calling before testing, I could create instances of the services like above.
Get DOM by CSS ID
For testing, I wanted to get DOM. I could get them by some ways.first-page.component.spec.ts
...
describe('FirstPageComponent', () => {
...
let loadButton: HTMLButtonElement;
let saveButton: HTMLButtonElement;
beforeEach(async(() => {
...
}));
beforeEach(() => {
...
fixture = TestBed.createComponent(FirstPageComponent);
component = fixture.componentInstance;
// 1. fixture.debugElement.query + By.css
loadButton = fixture.debugElement.query(By.css('#load-button')).nativeElement;
// 2. fixture.nativeElement.querySelector
saveButton = fixture.nativeElement.querySelector('#save-button') as HTMLButtonElement;
// 3. document.getElementById (I shouldn't use because I couldn't get any changes of fixture)
saveButton = document.getElementById('save-button') as HTMLButtonElement;
fixture.detectChanges();
});
});
An important thing was if I changed DOM, I had to call "fixture.detectChanges();" or the changes wouldn' be reflected.
So some of the DOM what were created by DOM events had to be gotten their instances in test cases.
Add test cases
Call click event and test DOM values
first-page.component.spec.ts
...
it('should load and set products', () => {
loadButton.click();
fixture.detectChanges();
productList = fixture.debugElement.query(By.css('#product-list')).nativeElement;
expect(productList.childElementCount).toEqual(2);
productList.childNodes.forEach(n => {
const childElement = n as HTMLElement;
switch(childElement.className) {
case 'product-id':
expect(childElement.innerText).toEqual('ID: 1');
break;
case 'product-name':
expect(childElement.innerText).toEqual('NAME: hello');
break;
}
});
});
...
Spy alerts
To avoid showing window.confirm, or getting alerms in tests, I could use "spyOn".first-page.component.spec.ts
...
it('should not show alert when upload file is null', () => {
spyOn(window, 'confirm');
spyOn(window, 'alert');
component.saveFile();
expect(window.alert).not.toHaveBeenCalled();
});
...
Set files into an HTMLInputElement
first-page.component.spec.ts
Although I wanted to set files into an HTMLInputElement, I couldn't do this.
const file = new File([new Blob(['test'])], 'sample.txt');
const fileList: FileList = {
0: file,
length: 1,
item: (index) => file,
};
const inputElement = document.createElement('input');
inputElement.type = 'file';
// Error
inputElement.files = fileList;
To do that, I used "Object.setPrototypeOf" to overwite "files" property.
it('show alert after file uploading', () => {
spyOn(window, 'confirm');
spyOn(window, 'alert');
const file = new File([new Blob(['test'])], 'sample.txt');
const fileList: FileList = {
0: file,
length: 1,
item: (index) => file,
};
Object.setPrototypeOf(fileList, Object.create(window.FileList.prototype));
const inputElement = document.createElement('input');
inputElement.type = 'file';
Object.defineProperty(inputElement, 'files', {
value: fileList,
writable: false,
});
component.fileChanged(inputElement);
component.saveFile();
expect(window.alert).toHaveBeenCalled();
});
コメント
コメントを投稿