首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >单元测试角9中的组件中的异步代码

单元测试角9中的组件中的异步代码
EN

Stack Overflow用户
提问于 2021-07-21 16:10:47
回答 1查看 244关注 0票数 0

更新2:结果显示,我的新测试很可能是在第一次加载时响应上一次测试的url参数。这显然不是我想要的,很可能意味着我需要改变路由。是在afterEach中重置路由参数的唯一解决方案吗?有更好的方法来处理这件事吗?

更新:

经过进一步的挖掘,我不再认为这是一个异步问题,但我不知道问题是什么。我认为这与路由有关,或者是订阅什么的。下面是我向组件抛出的一些控制台日志中的一个失败测试场景的示例。

代码语言:javascript
复制
//This is an emulated copy of what I see in the console

new request: 
> {pageId: 3, testCount: 1}
retrievePage: making the request with:
> {pageId: 3, testCount: 1}
new request:
> {id: 2, testCount: 2}
retrieveItem: making the request with:
> {id: 2, testCount: 2}
retrieveItem: made the request with: 
> {id: 2, testCount: 2}
FAILED MyComponent when the url matches item/:id should load the appropriate item
retrievePage: made the request with:
> {pageId: 3, testCount: 1}
> error properties: Object({ originalStack: 'Error: Expected spy service.retrievePage not to have been called.....

我已经更新了下面的代码片段,以反映我添加的控制台日志。

从日志中可以看到,由于某种原因,params订阅将在第一次测试中第二次运行(第一次测试运行并完成良好,记录“新请求”和“生成.”/“生成.”。只发一次信息)。我不知道是否有一种方法来确定使用哪个测试组件运行(如果由于某种原因生成了新组件并响应上一次测试中的params )或什么。我真的不确定问题是什么,也不知道如何进一步调试。

原始问题

在测试运行一些异步代码的组件时,我遇到了问题。由于NDA,我不能发布实际的代码,但是我提供了一个简单的表示,它尽可能接近我的真实代码。

我有一个组件,它运行类似于这样的异步代码(只包含相关的位,默认导入/设置是隐含的):

代码语言:javascript
复制
...
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { MyService } from 'services/my-service';

@Component({
    ...
})
export class MyComponent implements OnInit, OnDestroy {
    private paramsSubscription: Subscription;

    constructor(private route: ActivatedRoute, private service: MyService) {}

    ngOnInit(): void {
        this.paramsSubscription = this.route.params.subscribe(async params => {
            console.log('new request: ', params);
            let result;
            let itemId = params.id;
            
            if (params.pageId) {
                
                console.log('retrievePage: making the request with: ', params);
                let page = await this.service.retrievePage(params.pageId).toPromise();
                console.log('retrievePage: made the request with: ', params);
                itemId = page.items[0].id;
            }

            if (itemId) {
                console.log('retrieveItem: making the request with: ', params);
                result = await this.service.retrieveItem(itemId).toPromise();
                console.log('retrieveItem: made the request with: ', params);
            }
            ...
        });
    }
    ngOnDestroy() {
        if (this.paramsSubscription) this.paramsSubscription.unsubscribe();
    }
}

然后我有这样的单元测试(也是样例代码):

代码语言:javascript
复制
...
import { fakeAsync, tick } from '@angular/core/testing';
import { of, ReplaySubject } from 'rxjs';
import { ActivatedRoute, Params } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { MyService } from 'services/my-service';
import { MyComponent } from './my.component';

describe('MyComponent', () => {
    let component: MyComponent;
    let fixture: ComponentFixture<MyComponent>;

    let service = jasmine.createSpyObj('MyService', {
        retrieveItem: of([]), 
        retrievePage: of([])
    });
    let route: ActivatedRoute;
    let routeParams = new ReplaySubject<Params>(1);
    
    let testCount = 0;

    let item = {
        id: 2,
        ...
    };

    let page = {
        id: 3,
        items: [item]
        ...
    };

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [MyComponent],
            imports: [RouterTestingModule],
            providers: [
                { provide: MyService, useValue: service },
                { provide: ActivatedRoute, useValue: 
                    jasmine.createSpyObj('ActivatedRoute',[], {
                        params: routeParams.asObservable()
                    }) 
                },
            ]
        }).compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(MyComponent);
        component = fixture.componentInstance;

        service = TestBed.inject(MyService);
        route = TestBed.inject(ActivatedRoute);
        
        testCount++;

        fixture.detectChanges();
    });
    
    afterEach(() => {
        service.retrieveItem.calls.reset();
        service.retrievePage.calls.reset();
    });

    it('should create', () => {
        expect(component).toBeTruthy();
    });
    

    describe('when the url matches: item/:id', () => {
        beforeEach(fakeAsync(() => {
            routeParams.next({id: item.id, testCount: testCount});
            tick();
            fixture.detectChanges();
        }));

        it('should load the appropriate item', () => {
            expect(service.retrieveItem).toHaveBeenCalledWith(item.id);
            expect(service.retrievePage).not.toHaveBeenCalled();
        });
        ...
    });

    describe('when the url matches: item', () => {
        beforeEach(fakeAsync(() => {
            routeParams.next({testCount: testCount});
            tick();
            fixture.detectChanges();
        }));

        it('should load the first item', () => {
            expect(service.retrievePage).not.toHaveBeenCalled();
            expect(service.retrieveItem).toHaveBeenCalledWith(1);
        });
        ...
    });

    describe('when the url matches: item/:pageId', () => {
        beforeEach(fakeAsync(() => {
            routeParams.next({pageId: page.id, testCount: testCount});
            tick();
            fixture.detectChanges();
        }));

        it('should load the item from the page', () => {
            expect(service.retrievePage).toHaveBeenCalledWith(page.id);
            expect(service.retrieveItem).toHaveBeenCalledWith(page.items[0].id);
        });
        ...
    });
});

我遇到的问题是在我的单元测试中,我的组件的等待调用还没有完成,直到它进入下一个单元测试,所以我的expect调用不能正常工作。当我的测试井然有序的时候,一切都很好。但有时我的测试以不同的顺序运行--比如1、3、2。当它们这样做时,测试2失败了,因为它希望使用id 2调用retrieveItem,但是前面的调用用id 1触发了它。(这只是一个人为的例子,我的实际代码比这个复杂)

我想知道的是,我如何告诉茉莉花等待在我的组件中触发的调用返回。我试过查看关于jasmine https://jasmine.github.io/tutorials/async的异步代码的教程,但是这些教程似乎都提到了在我的测试中手动进行异步调用,这不是我想要做的。

带蜱()的fakeAsync是我试图让它等待路由和一切完成的尝试,但它似乎没有解决我的问题。有人知道我做错了什么或如何解决我的问题吗?

我也尝试过:

tick(10)

  • using fixture.whenStable().then().then() => { //expect

  • })

另外,如果我将routeParams.next({})添加到我的afterEach()中,它解决了这个问题,但我觉得这违背了测试是随机顺序的目的,因为这意味着只有当您来自没有params的情况下,我的代码才能工作,在我的实际实现中,您可以在url中使用任何不同的参数(例如,我可以从pageId: 1到id: 2,而没有间隔)。

更多信息:在我的实际代码中,情况如下:

代码语言:javascript
复制
//This is pseudo code
params.subscribe
    if(params.first) param1 = params.first;
    if(params.second) param2 = params.second;
    if (param1) {
        item = function1();
        param2 = item.param2;
    }
    if(param2) {
        otherItem = await function2();
    } else if (item) {
        result = await function3();
    }

我的测试正在检查function1、function2和function3是否在activatedRoute通过各种参数时被调用。我在组件中添加了控制台日志,以确认test2将启动,并且在test2启动后将调用来自test1的function2。因此,我的检查,以确保function2没有被调用,是失败的,因为它是从test1延迟。

任何建议或建议都是非常感谢的。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-07-21 17:55:43

因此,经过大量的挖掘和搜索,我和我自己设法找到了解决我的问题的方法,所以我在这里分享,以防其他人像我一样困惑。

https://angular.io/guide/testing-components-scenarios#testing-with-activatedroutestub

在使用activatedRoutes的角度文档中,我注意到它们首先触发路由,然后创建组件。从观察应用程序的正常工作方式(而不是在测试期间)来看,这更符合实际功能的角度--路由在创建组件之前就已经发生了。基于这一事实,我最终将我的测试重做如下所示:

代码语言:javascript
复制
...
import { async } from '@angular/core/testing';
import { of, ReplaySubject } from 'rxjs';
import { ActivatedRoute, Params } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { MyService } from 'services/my-service';
import { MyComponent } from './my.component';

describe('MyComponent', () => {
    let component: MyComponent;
    let fixture: ComponentFixture<MyComponent>;

    let service = jasmine.createSpyObj('MyService', {
        retrieveItem: of([]), 
        retrievePage: of([])
    });
    let route: ActivatedRoute;
    let routeParams = new ReplaySubject<Params>(1);
    
    let testCount = 0;

    let item = {
        id: 2,
        ...
    };

    let page = {
        id: 3,
        items: [item]
        ...
    };

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [MyComponent],
            imports: [RouterTestingModule],
            providers: [
                { provide: MyService, useValue: service },
                { provide: ActivatedRoute, useValue: 
                    jasmine.createSpyObj('ActivatedRoute',[], {
                        params: routeParams.asObservable()
                    }) 
                },
            ]
        }).compileComponents();
    }));

    beforeEach(() => {
        service = TestBed.inject(MyService);
        route = TestBed.inject(ActivatedRoute);
    });
    
    function createComponent() {
        fixture = TestBed.createComponent(MyComponent);
        component = fixture.componentInstance;

        fixture.detectChanges();
    }
    
    afterEach(() => {
        service.retrieveItem.calls.reset();
        service.retrievePage.calls.reset();
    });

    it('should create', () => {
        createComponent();
        expect(component).toBeTruthy();
    });
    

    describe('when the url matches: item/:id', () => {
        beforeEach(async () => {
            routeParams.next({id: item.id, testCount: testCount});
            createComponent();
        });

        it('should load the appropriate item', () => {
            expect(service.retrieveItem).toHaveBeenCalledWith(item.id);
            expect(service.retrievePage).not.toHaveBeenCalled();
        });
        ...
    });

    describe('when the url matches: item', () => {
        beforeEach(async () => {
            routeParams.next({testCount: testCount});
            createComponent();
        });

        it('should load the first item', () => {
            expect(service.retrievePage).not.toHaveBeenCalled();
            expect(service.retrieveItem).toHaveBeenCalledWith(1);
        });
        ...
    });

    describe('when the url matches: item/:pageId', () => {
        beforeEach(async () => {
            routeParams.next({pageId: page.id, testCount: testCount});
            createComponent();
        ));

        it('should load the item from the page', () => {
            expect(service.retrievePage).toHaveBeenCalledWith(page.id);
            expect(service.retrieveItem).toHaveBeenCalledWith(page.items[0].id);
        });
        ...
    });
});

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68472991

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档