Angular 2服务单元测试注入后不运行回调函数

14

我已经为我的组件创建了单元测试,但是想要一些针对单独服务的测试。然而,当我尝试注入它们时(被测试的服务方法不是异步的),出现了问题。

describe('SearchService', () => {
    beforeEach(() => {
        TestBed.configureTestingModule({
            providers: [
                SearchService
            ]
        });

    });


    it("should build Url String", () => {
        inject([SearchService], (searchService: SearchService) => {
            spyOn(searchService, 'buildURL');
            console.log("should be logging like a boss");
            searchService.buildURL("test", "coursename", 2);
            expect(searchService.buildURL).toHaveBeenCalled();
            expect(searchService.buildURL("test", "coursename", 2)).toBe(['1']);
            expect(searchService.buildURL("test", "coursename", 2)).toBeFalsy();
        });
    });

});

Inject实际上从未运行回调函数!测试识别该语句,但通过而没有错误。

内部的console.log语句未执行,并且设计为失败的测试也能够通过。因此,我认为inject未能成功运行。


如果程序无法运行,那么一定存在错误。如果您确定没有错误,请提供 http://stackoverflow.com/help/mcve。 - Estus Flask
@estus 没有错误。事实上,整个测试都通过了。我可以在 inject 之外控制台记录日志,但是在 console.log 内部却从未运行。 - deek
@estus,改进了它并将其削减到最少。 - deek
请提供一种复制问题的方法——Plunker、Stackblitz等。因为代码看起来没问题,除了你以外没有人能弄清楚哪里出了问题。 - Estus Flask
1
你确定问题不是出在多余的Clojure上吗?尝试移除它,将代码修改为it("should build Url String", inject([SearchService], (searchService: SearchService) => { - anteAdamovic
显示剩余2条评论
2个回答

3

编辑:添加了如何使用替换原始示例的HTTP调用存根数据完整示例来单元测试Angular 2/4服务。在我看来,这是优秀的单元测试服务的例子,与官方和第三方指南略有不同。

编辑:重新阅读了官方指南,并在上面的评论中提到@AnteJablanAdamović,指出它应该是

it('should tell ROUTER to navigate when hero clicked',
  inject([Router], (router: Router) => { // ...
}));

https://angular.io/guide/testing#the-inject-function

我不确定您是否可以将其包装在fakeasync(为什么不呢?)或异步回调中,但这是我的原始问题的正确答案(为什么没有人发现这个问题并获得50+赏金和10+赞成票?!)。

然而,下面的策略是一种更加清洁/快速的方法,在BeforeEach中包含它,而不是将inject粘贴到每个"it"语句中;

很遗憾Karma或angular没有抛出任何错误或警告标志。

以下是我提供的原始答案,但也可以作为替代方法:


我使用testBet.get在beforeEarch中注入服务:在我看来比大多数指南建议的方法要好得多。

如果您在测试服务时遇到问题,请尝试此指南:涵盖具有依赖关系的简单或复杂服务:

http://www.kirjai.com/testing-angular-services-with-dependencies/

 describe('SearchService', () => {
// IMPORTANT - declase variables we'll set in before each so every "it statement // can reach them

    let searchService: SearchService;
    let backend: MockBackend;
    let setupConnections;


        class MockActivatedRoute extends ActivatedRoute {
            constructor() {
                super();
                this.params = Observable.of({ 'searchterm': '*', 'sorttype': 'relevant', 'pagenumber': 1, 'facet': '' });
            }
        }
        const MockRouter = {
            navigate: (x) => ({
                then: () => ({})
            })
        };
        beforeEach(() => {
            TestBed.configureTestingModule({
                imports: [HttpModule],
                providers: [
// below required for HTTP substitution testing
                    MockBackend,
                    BaseRequestOptions,
                    {
                        provide: Http,
                        useFactory: (backend: MockBackend, options: BaseRequestOptions) => new Http(backend, options),
                        deps: [MockBackend, BaseRequestOptions]
                    },
                    AnalyticsService,
                    { provide: ActivatedRoute, useClass: MockActivatedRoute },
                    {
                        provide: Router,
                        useValue: MockRouter
                    },
                    SearchService
                ]
            });

// set our values in before each and use Testbed to inject services
        searchService = TestBed.get(SearchService);
        backend = TestBed.get(MockBackend);

你可以像上面设置setupConnections的指南链接中的if语句一样设置路径,但除非你在调用时做了一些不寻常的事情,否则你不需要让路径匹配,所以这是可以的。
 setupConnections = (backend: MockBackend, options: any) => {
            backend.connections.subscribe((connection: MockConnection) => {
                const responseOptions = new ResponseOptions(options);
                const response = new Response(responseOptions);
                connection.mockRespond(response);
            });
        };

        });

请注意async而不是fakeAsync!!!通常我在组件单元测试中使用fakeAsync很好,但是我在以这种方式对这些服务进行单元测试时遇到了一些错误,结果因人而异。
    it('should get suggestions for search drop down and match of mock results for test', async(() => {
        console.log('running get Suggestions');
// here we set out HTTP data response stub: put return data in body
        setupConnections(backend, {
            body: {
                suggestions:
                ["6-minute walk test",
            },
            status: 200
        });
// resolve HTTP call with subscribe and do test in call back.
        searchService.getSuggestions('test').subscribe((x) => {
            console.log(x);
            expect(x).toEqual(['6-minute walk test']);
        });

    });

3
你在clojure中嵌套了额外的1个,这就是它无法正常工作的原因。
it("should build Url String", () => {
        inject([SearchService], (searchService: SearchService) => {
            spyOn(searchService, 'buildURL');
            console.log("should be logging like a boss");
            searchService.buildURL("test", "coursename", 2);
            expect(searchService.buildURL).toHaveBeenCalled();
            expect(searchService.buildURL("test", "coursename", 2)).toBe(['1']);
            expect(searchService.buildURL("test", "coursename", 2)).toBeFalsy();
        });
    });

将其更改如下以使其正常工作:
it("should build Url String", inject([SearchService], (searchService: SearchService) => {
            spyOn(searchService, 'buildURL');
            console.log("should be logging like a boss");
            searchService.buildURL("test", "coursename", 2);
            expect(searchService.buildURL).toHaveBeenCalled();
            expect(searchService.buildURL("test", "coursename", 2)).toBe(['1']);
            expect(searchService.buildURL("test", "coursename", 2)).toBeFalsy();
    })
);

原因是,由于您在另一个clojure中执行inject,它将在另一个作用域内执行。it的第二个参数应该是带有测试的函数,但由于您传递了一个空的clojure,它将简单地解析为true。
以下是发生的示例:
() => { // calling this clojure it will return null/undefined
  () => { // calling this clojure it will return '1'
    return '1';
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接