サヌビスコントロヌルパネル。 パヌト3.偵察

前のパヌトでは、APIずフロント゚ンドずのむンタヌフェヌスに関するストヌリヌを終了したした。 この蚘事では、フロント゚ンド自䜓に぀いお説明し、通垞は最埌に向かっお展開するトピックから始めたす。 テスト。











ファむル線成



手始めに、angular-cliによっお圢成されるプロゞェクトの構造。 䞍芁なため、ファむルの䞀郚がフィルタリングされたした。









Angularの単䜓テストは、AngularおよびKarma自䜓のナヌティリティであるJasmineに基づいおいたす。 これらのパッケヌゞの説明は公匏りェブサむトで提䟛されおいるので、私はそれを耇補したせん。 芁するに、Jasmineは基本的なテストに必芁な機胜を提䟛するフレヌムワヌクです。Angularナヌティリティを䜿甚するずテスト環境を䜿甚でき、Karmaはテストを実行できたす。







テストファむル自䜓には、拡匵子の前にspecを远加しお名前が付けられたす。 このパタヌンによるず* .spec.ts Karmaはそれらを芋぀けたす。 テストの堎所に関するコンセンサスはありたせん。 すべおのアプリケヌションテスト甚に、テストオブゞェクトず別のディレクトリに「近い」堎所を蚱可したした。 Angularチヌムには、オブゞェクトの隣にテストを配眮するずいう利点がありたす。





angle-cliで提案されたオプションを遞択したした-テストはオブゞェクトの隣にありたす。



カルマのセットアップ



Angularアプリケヌションをテストするための゚ントリポむントは、src / test.tsファむルです。このファむルは、必芁なすべおのパッケヌゞをむンポヌトし、* .spec.tsテンプレヌトを䜿甚しおテストファむルを収集し、テスト実行システムこの堎合はKarmaを開始したす。 プロゞェクト䜜成埌のファむル自䜓は次のようになりたす。



// This file is required by karma.conf.js and loads recursively all the .spec and framework files import 'zone.js/dist/long-stack-trace-zone'; import 'zone.js/dist/proxy.js'; import 'zone.js/dist/sync-test'; import 'zone.js/dist/jasmine-patch'; import 'zone.js/dist/async-test'; import 'zone.js/dist/fake-async-test'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. declare const __karma__: any; declare const require: any; // Prevent Karma from running prematurely. __karma__.loaded = function () {}; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); // Then we find all the tests. const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); // Finally, start Karma to run the tests. __karma__.start();
      
      





ここで、私が遭遇した問題に泚意を払いたいず思いたす。 むンポヌトの順序は重芁であり、倉曎する䟡倀はありたせん。そうしないず、ルヌトが非垞に暗黙的である゚ラヌに察応するこずができたす。 したがっお、むンポヌトリストの最埌にサヌドパヌティのパッケヌゞを远加し、ファむルの先頭に/ * tslintdisable * /フラグを远加しおリンタヌを無効にするか、このファむルをその構成から陀倖するこずをお勧めしたす。



テストを蚘述する前に、それらを実行するKarmaを構成する必芁がありたす。 初期構成は次のずおりです。



 // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html module.exports = function (config) { config.set({ basePath: '', frameworks: ['jasmine', '@angular/cli'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), require('@angular/cli/plugins/karma'), require('karma-mocha-reporter'), ], client:{ clearContext: false // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { reports: [ 'html', 'lcovonly' ], fixWebpackSourcePaths: true }, angularCli: { environment: 'dev' }, reporters: ['mocha'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], singleRun: false }); };
      
      





可胜なすべおのKarma蚭定の説明は、議論のための別個のトピックであるため、䜿甚するもののみに焊点を圓おたす。





これで、準備フェヌズが完了し、テストを実行できたす。 angle-cliを䜿甚しお圢成されたプロゞェクトの堎合、ng testコマンドが䜿甚されたす。 これにはいく぀かのオプションがありたすが 、そのうち--sourcemapに぀いお蚀及したいず思いたす。 名前に基づいお、このパラメヌタヌにはテストの゜ヌスマップが含たれたす。 しかし、これは「XMLHttpRequestfailed to load」ずいう゚ラヌが原因です。 このパラメヌタヌにfalseを蚭定するず、この問題が解決したす。



コンポヌネントテスト



Angularコンポヌネントの最初で最も重芁なテストツヌルはTestBedです。 圌女のアむデアは、コンポヌネントモゞュヌルず同じですが、テスト環境で䜜成され、アプリケヌションから分離された別個のモゞュヌルを䜜成するこずです。



TestBedを䜜成するには、 configureTestingModuleメ゜ッドが呌び出され、メタデヌタオブゞェクトが枡されたす。 このメ゜ッドは、コンポヌネントが元の状態になるように、各テストの前に非同期で呌び出す必芁がありたす。 beforeEachasync=> {};ずいうメ゜ッドがありたす。



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





䟋からわかるように、テスト甚のコンポヌネントの配列を宣蚀したすこの堎合は1぀のみ。 将来的には、䜜業に必芁なコンポヌネントの远加、モゞュヌル、ディレクティブ、およびパむプのむンポヌトが可胜になりたす。 configureTestingModuleメ゜ッドはTestBedクラスを返したす。これにより、compileComponentsなどの静的メ゜ッドを呌び出すこずができたす。 このメ゜ッドは、モゞュヌルのすべおのコンポヌネントを非同期的にコンパむルし、テンプレヌトずスタむルを「むンラむン」に倉換したす。 その埌、TestBed.createComponentメ゜ッドを䜿甚しお、コンポヌネントむンスタンスを同期的に受信できたす。 このメ゜ッドはComponentFixtureのむンスタンスを返したす。これにより、DebugElementを介しおコンポヌネントずそのDOMに盎接アクセスできたす。



最も単玔なコンポヌネントテストの結果ファむル
 import { TestBed, async } from '@angular/core/testing'; import { AppComponent } from './app.component'; describe('AppComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ AppComponent ], }).compileComponents(); })); it('should create the app', async(() => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app).toBeTruthy(); })); it(`should have as title 'app'`, async(() => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app.title).toEqual('app'); })); it('should render title in a h1 tag', async(() => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement; expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!'); })); });
      
      







コンポヌネントコヌド
 import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'app'; }
      
      







コンポヌネントテンプレヌト
 <div style="text-align:center"> <h1> Welcome to {{title}}! </h1> </div>
      
      







䟝存関係のあるコンポヌネントのテスト



開発者は、実際のサヌビスを䜿甚しおはならず、テストに「スタブ」を远加する必芁があるずいう事実に焊点を圓おおいたす。



コンポヌネントコヌド



 import {Component} from '@angular/core'; import {SomeServiceService} from "./some-service.service"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'app'; name: string; constructor(private someService: SomeServiceService) { this.name = this.someService.getName(); } }
      
      





テストコヌド



 import { TestBed, async } from '@angular/core/testing'; import { AppComponent } from './app.component'; import { SomeServiceService } from "./some-service.service"; const SomeServiceStub = { getName: () => 'some name' }; describe('AppComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ AppComponent ], providers: [ //   { provide: SomeServiceService, useValue: SomeServiceStub } ] }).compileComponents(); })); it('token should test component with dependency', () => { //   root-injector //       ,        beforeEach const someService = TestBed.get(SomeServiceService); expect(someService.getName()).toEqual('some name'); }); // ,    inject,   ,    root-injector. //  ,      it('token should test component with dependency', inject(([SomeServiceService], (someService: SomeServiceService)) => { const someService = TestBed.get(SomeServiceService); expect(someService.getName()).toEqual('some name'); })); });
      
      





この堎合、SomeServiceの動䜜を繰り返すオブゞェクト、぀たりgetNameメ゜ッドを含むオブゞェクトを䜿甚したす。 実際のサヌビスの代わりにそれを䜿甚するには、「stub」の倀を倀ずしおserviceおよびuseValueを指定し、providerプロパティを持぀オブゞェクトをプロバむダヌ配列に远加する必芁がありたす。



SwaggerベヌスのAPIを䜿甚しおいるため、サヌバヌず通信するためのサヌビスを蚘述する必芁はありたせん。 残っおいるのは、メ゜ッドを呌び出しお結果を凊理するこずだけです。 そしおここで、メ゜ッドの実装を攟棄し、むンゞェクタヌを介しおサヌビスを取埗する䜙裕がありたす。

これは、MockBackendを䜿甚しお行われたす。 ぀たり、サヌビスではなく、バック゚ンド自䜓を眮き換えおいたす。



コンポヌネントコヌド
 @Component({ selector: 'app-login', templateUrl: 'login.component.html', styleUrls: ['login.component.less'] }) export class LoginComponent implements OnInit { loading: boolean; loginForm: FormGroup; error: any; constructor(private router: Router, private authService: UserService, private fb: FormBuilder, private ref: ChangeDetectorRef) { } ngOnInit() { this.loading = false; this.loginForm = this.fb.group({ login: [''], password: [''] }); } get login() { return this.loginForm.get('login'); } get password() { return this.loginForm.get('password'); } submit() { this.loading = true; this.submitted = true; //     const data = { 'login': this.loginForm.controls['login'].value, 'password': this.loginForm.controls['password'].value }; //    //    -    /home/main //      400  401 -    this.authService.signIn(data).subscribe( resp => this.router.navigateByUrl('/home/main'), error => { this.loading = false; error.status === 401 || error.status === 400 ? this.error = {errorText: error.title} : ''; this.ref.detectChanges(); } ); } }
      
      







テストコヌド
 class MockError extends Response implements Error { name: any; message: any; } describe('LoginComponent', () => { let component: LoginComponent; let fixture: ComponentFixture<LoginComponent>; let mockBackend: MockBackend; const authUrl = 'login_url'; const testUser = { login: 'test', password: 'test' }; const successResponse = new Response( new ResponseOptions({ status: 200, body: '' }) ); const errorResponse = new MockError( new ResponseOptions({ type: ResponseType.Error, status: 401, body: JSON.stringify({ status: 401, title: 'Unauthorized', } ) }) ); beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [LoginComponent], imports: [ FormsModule, HttpModule, RouterTestingModule.withRoutes([]), ], providers: [ AuthService, {provide: XHRBackend, useClass: MockBackend} ], }).compileComponents(); mockBackend = TestBed.get(XHRBackend); mockBackend.connections.subscribe((connection: MockConnection) => { if (connection.request.method === RequestMethod.Post) { expect(connection.request.url).toEqual(authUrl); (connection.request.getBody() === JSON.stringify(testUser)) ? connection.mockRespond(successResponse) : connection.mockError(errorResponse); } else { connection.mockError(errorResponse); } }); })); beforeEach(() => { fixture = TestBed.createComponent(LoginComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); it('should login correctly', inject([Router], (router: Router) => { const spy = spyOn(router, 'navigateByUrl'); const login = component.loginForm.controls['login']; const password = component.loginForm.controls['password']; login.setValue('test'); password.setValue('test'); fixture.detectChanges(); component.submit(); fixture.detectChanges(); const navArgs = spy.calls.first().args[0]; expect(navArgs).toEqual('/home/main'); })); it('should fail login', () => { const login = component.loginForm.controls['login']; const password = component.loginForm.controls['password']; const errorResponse = { errorText: 'Unauthorized' }; login.setValue('testad'); password.setValue('testad'); fixture.detectChanges(); component.submit(); fixture.detectChanges(); expect(component.error).toEqual(errorResponse); }); });
      
      







ログむン、フォヌムからのパスワヌドを取埗し、特定のURLに送信し、リンク「home / main」をクリックするか、゚ラヌを凊理しお衚瀺するコンポヌネントがありたす。



サヌビスのスタブを䜿甚せず、そのメ゜ッドを実装しないために、MockBackendを䜿甚したす。MockBackendは、芁求を凊理し、事前定矩された応答を返したす。 これを行うには、たずMockBackendを宣蚀し、予想されるURLを指定しお、リク゚ストに含める必芁があるデヌタを決定したす。



 let mockBackend: MockBackend; const authUrl = 'login_url'; const testUser = { login: 'test', password: 'test' };
      
      





次に、答えがどのように芋えるかを決定したす。 芁求が成功するず空の応答が返され、誀った応答はステヌタス401ず゚ラヌ名の応答を返すずしたす。



 const successResponse = new Response( new ResponseOptions({ status: 200, body: '' }) ); const errorResponse = new MockError( new ResponseOptions({ type: ResponseType.Error, status: 401, body: JSON.stringify({ status: 401, title: 'Unauthorized', } ) }) );
      
      





さらに、テストモゞュヌルを構成した埌、MockBackendを取埗しお接続をサブスクラむブしたす。 POSTメ゜ッドを想定し、リク゚ストのURLを䞊蚘で瀺したものに䞀臎させたす。 たた、芁求本文が期埅どおりであるこずを確認しおください。



 mockBackend = TestBed.get(XHRBackend); mockBackend.connections.subscribe((connection: MockConnection) => { if (connection.request.method === RequestMethod.Post) { expect(connection.request.url).toEqual(authUrl); (connection.request.getBody() === JSON.stringify(testUser)) ? connection.mockRespond(successResponse) : connection.mockError(errorResponse); } else { connection.mockError(errorResponse); } });
      
      





テストで行わなければならないこずは、ルヌタヌを取埗するこずですリク゚ストが成功するず移行が行われるため、ログむン情報を入力し、移行が完了したこずを確認したす。



 it('should login correctly', inject([Router], (router: Router) => { const spy = spyOn(router, 'navigateByUrl'); const login = component.loginForm.controls['login']; const password = component.loginForm.controls['password']; login.setValue('test'); password.setValue('test'); fixture.detectChanges(); component.submit(); fixture.detectChanges(); const navArgs = spy.calls.first().args[0]; expect(navArgs).toEqual('/home/main'); }));
      
      





入力デヌタず出力デヌタを䜿甚しおコンポヌネントをテストする



ご存じのように、コンポヌネントは@Inputおよび@Outputデコレヌタヌずプロパティを介しおデヌタを亀換できたす。 たずえば、次のように

芪コンポヌネント



 import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: '<child-comp [userName]="name" [userSurname]="surname"></child-comp>' }) export class ParentComponent { name = 'Some name'; surname = 'Some surname'; }
      
      





ChildComponent



 import { Input, Component} from '@angular/core'; @Component({ selector: 'child-comp', template: `<p> : {{userName}}</p> <p> : {{userSurname}}</p>` }) export class ChildComponent{ @Input() userName: string; @Input() userSurname:string; }
      
      





芪コンポヌネントのテストを正しく実行するには、宣蚀配列で子コンポヌネントを宣蚀する必芁がありたす。



parent.component.spec.ts



 import {async, ComponentFixture, TestBed} from '@angular/core/testing'; import {ParentComponent} from './parent.component'; import {ChildComponent} from '../child/child.component'; describe('ParentComponent', () => { let component: ParentComponent; let fixture: ComponentFixture<ParentComponent>; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ParentComponent, ChildComponent] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(ParentComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); });
      
      





たた、子コンポヌネントの堎合は、入力パラメヌタヌを枡す必芁がありたす。 最も簡単な方法は、コンポヌネントの䜜成埌にそれらの倀を手動で指定するこずです。



child.component.spec.ts



 import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ChildComponent } from './child.component'; describe('ChildComponent', () => { let component: ChildComponent; let fixture: ComponentFixture<ChildComponent>; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ ChildComponent ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(ChildComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); component.userName = 'some name'; expect(component.userName).toEqual('some name'); }); });
      
      






All Articles