어떻게 방관자를 사용하여 간단한 각도 포인트 테스트를 작성합니까
Spectator는 내장된 각도 테스트 프레임워크를 봉하여 간단하지만 기능이 강한 API를 제공하여 샘플 파일을 줄이고 테스트를 더욱 뚜렷하고 읽을 수 있도록 하는 라이브러리이다.
본고에서, 나는 당신에게 '방관자' 가 어떻게 당신이 통합 테스트를 쉽게 작성하는 데 도움을 줄 수 있는지 보여 드리겠습니다.
코드로 직접 이동하려면 myGitHub와 onStackBlitz에서 찾을 수 있습니다.
접근
통합 테스트를 작성할 때, 나는 최종 사용자가 응용 프로그램을 사용하는 방식과 비슷한 테스트를 작성하는 것을 더욱 좋아한다.이것은 실천에서 다음과 같은 의미를 가진다.
예제 응용 프로그램
이 방법을 보여주기 위해 다음과 같은 작업을 수행하는 간단한 애플리케이션을 테스트했습니다.
전체 코드에 대한 자세한 내용은 StackBlitz를 참조하십시오. 그러나 다음은 Dell
PostsService
입니다.export class PostsService {
constructor(private dataService: DataService) {}
private posts = new BehaviorSubject<Post[]>(null);
private loading = new BehaviorSubject<boolean>(true);
posts$ = this.posts.asObservable();
loading$ = this.loading.asObservable();
// call the DataService which is responsible for making HTTP requests and then update the posts and loading states
load() {
this.loading.next(true);
return this.dataService.fetch().pipe(
tap(response => {
this.posts.next(response);
this.loading.next(false);
})
);
}
// return an observable with the filtered posts
getPosts(searchTerm: string, userId: number) {
const filterFunction = (post: Post) =>
(!searchTerm ||
post.title.toLowerCase().includes(searchTerm.toLowerCase())) &&
(!userId || post.userId === userId);
return this.posts$.pipe(map(posts => posts.filter(filterFunction)));
}
}
저는 여기서 상세하게 소개할 생각은 없습니다. (세부 사항을 실현하는 것은 사실상 저희 테스트에 중요하지 않습니다.) 그러나 PostsService
저희 상태를 저장하고 POST 데이터를 불러오고 필터하는 것을 책임집니다.그러나 이 프로그램은 HTTP 요청을 보내기 위해
DataService
를 사용했다는 점을 지적해야 한다.어쨌든, HTTP 요청을 보내는 단독 서비스를 만드는 것은 최선의 실천이지만, 시뮬레이션 서비스가 시뮬레이션 AngularHTTPClient
보다 간단하기 때문에 테스트를 더욱 쉽게 할 수 있다는 것을 알았다.다음은 Dell
PostsComponent
입니다.export class PostsComponent implements OnInit {
searchTermControl = new FormControl('');
userFilterControl = new FormControl(null);
posts$: Observable<Post[]>;
loading$: Observable<boolean>;
constructor(private service: PostsService) {}
ngOnInit(): void {
// request the posts data
this.service.load().subscribe();
// when the searchterm or the user filter changes get the filtered posts based on the filter
this.posts$ = combineLatest(
this.searchTermControl.valueChanges.pipe(
debounceTime(300),
startWith('')
),
this.userFilterControl.valueChanges.pipe(startWith(null))
).pipe(
switchMap(([searchTerm, userId]) => {
return this.service.getPosts(searchTerm, userId);
})
);
// loading state observable
this.loading$ = this.service.loading$;
}
}
테스트 설정
우선, 우리는 우리의 프로젝트에 관중을 설치해야 한다.
npm install @ngneat/spectator --save-dev
네가 Jasmine을 사용하든 Jest를 사용하든'방관자'는 모두 상자를 열면 바로 사용할 수 있고 별도의 설정이 없다. 그러나 이 예에 대해 나는 Jasmine를 계속 사용할 것이다.posts.component.spec.ts
에서 우리는 먼저 방관자createComponentFactory
기능 설정 테스트를 사용했다.여기서, 우리는 시청자들에게 우리가 어떤 구성 요소를 테스트하고 있는지 알려주고, 구성 요소에 필요한 모든 모듈을 가져옵니다.우리의 예에서 구성 요소는 FormsModule
와 ReactiveFormsModule
에 의존하기 때문에 imports
그룹에 추가합니다.여기에는 사용자가 어떻게 설정할 것인지
ngModule
와 같이 우리가 제공해야 할 모든 하위 구성 요소나 서비스도 설명하지만, 이 간단한 예에는 하위 구성 요소나 서비스가 없습니다.// posts.component.spec.ts
describe('PostsComponent', () => {
const createComponent = createComponentFactory({
component: PostsComponent,
imports: [FormsModule, ReactiveFormsModule],
mocks: [DataService],
detectChanges: false
});
...
});
통합 테스트를 작성할 때, 우리는 어떤 구성 요소와 서비스가 테스트에 포함되고, 어떤 것이 시뮬레이션될지 결정해야 한다.이 예에서 나는 나의 테스트가 PostsComponent
와 PostsService
를 포괄하기를 바란다.테스트 DataService
를 하고 싶지 않기 때문에, 모크스 그룹에 추가합니다.이렇게 하면 방관자는 자동으로 시뮬레이션DataService
을 하고 각 기능을 Jasmine spy(jasmine.createSpy()
로 변환합니다.나는
ngOnInit
직접 도망가고 싶지 않다. 왜냐하면 나는 먼저 DataService
에 어떻게 fetch
응답을 모의하는지 알려줘야 하기 때문에 detectChanges
를 false
로 설정했다.첫 번째 테스트 쓰기
이 설정이 있으면 우리는 첫 번째 테스트를 작성할 수 있다!다음 사항을 테스트합니다.
it('should load a list of posts for all users by default', fakeAsync(() => {
// create the test component
const spectator = createComponent();
// get the mocked instance of the DataService
const dataService = spectator.get(DataService);
// mock the fetch function to wait 100ms and return 2 posts
dataService.fetch.and.returnValue(
timer(100).pipe(
mapTo([
{
userId: 1,
id: 1,
title: 'First Post'
},
{
userId: 2,
id: 2,
title: 'Another Post'
}
])
)
);
// run ngOnInit
spectator.detectChanges();
// assert that the progress bar is showing
expect(spectator.query(MatProgressBar)).toExist();
expect(spectator.query(byText('First Post'))).not.toExist();
// get the user select element
const select = spectator.query(
byLabel('Filter by user')
) as HTMLSelectElement;
// assert that it is showing 'All' by default
expect(select).toHaveSelectedOptions(
spectator.query(byText('All')) as HTMLOptionElement
);
// advance the time 100ms to simulate the HTTP request being made
spectator.tick(100);
// assert that the progress bar is not showing and that both our posts are showing
expect(spectator.query(MatProgressBar)).not.toExist();
expect(spectator.queryAll(MatListItem).length).toEqual(2);
expect(spectator.query(byText('First Post'))).toExist();
expect(dataService.fetch).toHaveBeenCalledTimes(1);
}));
좋아, 여기 많은 일이 생겼어.먼저 Angular
fakeAsync
영역에서 테스트를 수행합니다.이것은 우리가 시간의 흐름을 제어함으로써 동기화 방식으로 비동기 코드를 쉽게 테스트할 수 있도록 한다.If you want to understand more about testing asynchronous code in Angular, I thoroughly recommend checking out this post by Netanel Basel.
이전에 만들어진 공장에서 테스트 구성 요소를 만든 후에 나는
fetch
에서 DataService
방법을 시뮬레이션했다.나는 HTTP 요청을 시뮬레이션하기 위해 100밀리 초를 기다린 다음 일련의 댓글을 보낼 observable로 되돌아오라고 말했다.이 시뮬레이션이 있어서 나는
spectator.detectChanges()
을 호출했는데 이것은 구성 요소에서 실행하는 것과 같다ngOnInit()
.이 점에서 진도표가 존재한다고 단언하고 싶습니다. 제 게시물이 나타나지 않았고 사용자 밑에 있는 목록에'모두'가 표시되었습니다. 저는 방관자의 사용자 정의 매칭기
toHaveSelectedOptions
를 사용하여 이 점을 쉽게 실현할 수 있습니다.그리고 저는
spectator.tick(100)
를 사용하여 타이머를 100밀리초 앞당겼습니다. 이때fetch
방법은 게시물 목록을 되돌려줍니다.마지막으로 나는 진도표가 이미 존재하지 않는다고 단언할 수 있다. 이 두 게시물도 존재한다.나는 또
fetch
방법이 단지 한 번 호출된 적이 있다고 단언했다.이 테스트에 대한 참고 사항:
PostsComponent
와 하나PostsService
로 나의 응용 프로그램을 설정했지만, 나의 응용 프로그램이 증가함에 따라, 나는 그것을 재구성하여 여러 개의 구성 요소나 다른 상태 관리 방법을 사용하기를 원할 수도 있다. 예를 들어 하나의 상태 관리 라이브러리를 포함한다.이런 방법의 장점은 내가 코드를 재구성할 수 있다는 데 있다. 나의 해결 방안이 여전히 호출DataService
된다면 나의 테스트는 변경할 필요가 없다!😄 spectator.query(byLabel('Filter by user'))
를 사용하면 이 점을 실현할 수 있다. 이것은 실제 사용자가 요소를 찾는 방식이지 요소id
를 통해 찾는 방식이 아니기 때문이다.그것은 또한 접근성을 시험하는 방향으로 은밀하게 발전하고 있다.게시물 필터링
우리는 여전히 댓글을 필터링하는 테스트를 해야 하기 때문에 마지막으로 몇 가지 예를 보고'방관자'가 얼마나 간단한지 봅시다.
사용자별로 필터링할 때, 내 프로그램은 선택 입력을 사용합니다.이를 위해, 나는 관중
selectOption
매칭기를 사용하여 나의 옵션을 선택할 수 있다.const select = spectator.query(
byLabel('Filter by user')
) as HTMLSelectElement;
spectator.selectOption(
select,
spectator.query(byText('User 2')) as HTMLOptionElement
);
selectOption
도 나중에 detectChanges()
하여 샘플 파일을 더 줄일 수 있습니다.👍검색어 필터를 테스트할 때 사용자 입력을 시뮬레이션해야 합니다.이를 위해 나는
typeInElement
조수를 사용할 수 있다.const input = spectator.query(byLabel('Filter by title'));
spectator.typeInElement('first', input);
spectator.tick(300);
검색 입력 변경에 300밀리초의 온스 제거 시간이 있기 때문에, 나는 spectator.tick(300)
타이머를 300밀리초 앞당겨 실행해야 한다. (테스트는 반드시 fakeAsync
구역에서 실행해야 앞에서 논의한 것처럼 작동할 수 있다.)결론
방관자를 사용하여 간단하고 읽기 쉬운 통합 테스트를 작성하는 방법을 보여 드리겠습니다. 이것은 사용자의 상호작용과 결과를 주목하는 것이지, 세부 사항을 실현하는 것이 아니라, 코드가 최종 사용자가 예상한 대로 작동할 것이라고 믿게 하는 것입니다.
전체 코드는 GitHub 또는 StackBlitz를 참조하십시오.
리소스
켄트 C 도드 - Write Fewer Longer Tests
켄트 C 도드 - Write tests. Not too many. Mostly integration
Netanel Basel-Testing Asynchronous Code in Angular Using FakeAsync
Reference
이 문제에 관하여(어떻게 방관자를 사용하여 간단한 각도 포인트 테스트를 작성합니까), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/cjcoops/how-to-write-simple-angular-integration-tests-with-spectator-1i1b텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)