【问题标题】:How mock object work in Angular jasmine unit test模拟对象如何在 Angular jasmine 单元测试中工作
【发布时间】:2018-04-21 18:35:47
【问题描述】:

我正在学习单元测试和 Angular,所以我都是初学者。 我已经参考了一些关于 Angular 单元测试 http 的教程和文章。

我无法理解 httpMock 使用 HttpTestingController 做了什么。我们调用实际服务的函数,那为什么我们称它为模拟呢? 什么是底层流程? 请参考一些文章以获得更好的理解。

提前致谢。

编辑: 这是我坚持使用 httpMock 的 issue

【问题讨论】:

    标签: angular angular-httpclient


    【解决方案1】:

    让我们以我的一个文件为例,好吗?

    import { SimpleConfiguration } from '../../../../../model/SimpleConfiguration';
    import { SessionService } from '../../../session/session.service';
    import { TestBed } from '@angular/core/testing';
    import { RouterTestingModule } from '@angular/router/testing';
    import { ConfigurationService } from './configuration.service';
    import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
    
    describe('ConfigurationService', () => {
    
      let httpMock: HttpTestingController;
      let service: ConfigurationService;
    
      const createFakeFile = (fileName: string = 'fileName'): File => {
        const blob = new Blob([''], { type: 'text/html' });
        blob['lastModifiedDate'] = '';
        blob['name'] = fileName;
        return <File>blob;
      };
    
      beforeEach(() => {
        TestBed.configureTestingModule({
          imports: [
            RouterTestingModule,
            HttpClientTestingModule
          ],
          providers: [ConfigurationService, SessionService]
        });
    
        httpMock = TestBed.get(HttpTestingController);
        service = TestBed.get(ConfigurationService);
      });
    
      it('should be created', done => {
        expect(service).toBeTruthy();
        done();
      });
    
      it('getConfigurations should GET on postes-sources/:postID/configuration', (done) => {
        service.getConfigurations(0).subscribe(res => done());
        const successRequest = httpMock.expectOne(service.URL + 'postes-sources/0/configurations');
        expect(successRequest.request.method).toEqual('GET');
        successRequest.flush(null);
        httpMock.verify();
      });
    
      it('uploadFile should POST on postes-sources/:postID/configuration', (done) => {
        service.uploadFile(0, createFakeFile(), new SimpleConfiguration()).subscribe(res => done());
        const successRequest = httpMock.expectOne(service.URL + 'postes-sources/0/configurations');
        expect(successRequest.request.method).toEqual('POST');
        successRequest.flush(null);
        httpMock.verify();
      });
    
      it('updateComment should POST on postes-sources/:postID/configurations/:confID', (done) => {
        service.updateConfiguration(0, 0, new SimpleConfiguration()).subscribe(res => done());
        const successRequest = httpMock.expectOne(service.URL + 'postes-sources/0/configurations/0');
        expect(successRequest.request.method).toEqual('POST');
        successRequest.flush(null);
        httpMock.verify();
      });
    
      it('getComponentInformations should GET on postes-sources/:postID/tranches/:trancheID/parametres', (done) => {
        service.getComponentInformations(0, 0).subscribe(res => done());
        const successRequest = httpMock.expectOne(service.URL + 'postes-sources/0/tranches/0/parametres');
        expect(successRequest.request.method).toEqual('GET');
        successRequest.flush(null);
        httpMock.verify();
      });
    });
    

    让我一步一步地向你详细解释。

    1. 我们从describe-ing 开始我们的测试。它允许我们对测试进行分组。在这种情况下,我们按功能对测试进行分组,我们的功能是一个名为 ConfigurationService 的服务。然后我们提供一个回调函数:这是 Jasmine 将运行的函数来运行您的测试。

    2. 接下来,我们声明我们的变量。在这里,我们声明了 2 个变量和 1 个函数:httpMockservicecreateFakeFile()。这些变量在整个测试组中都会有帮助,所以我们在顶层声明它们。

    3. 然后是beforeEach:在每次测试之前,都会运行这个函数,做一些事情。在这个中,它将创建一个 TestBed :这是一些样板代码,它将创建某种 Angular 模块,以允许您的测试运行,就像您的功能在真正的 Angular 应用程序中一样。

    在这个测试台中,你需要声明你的依赖项:因为我在测试一个 HTTP 服务,所以我必须导入 HTTP 测试模块,因为我的测试使用路由,所以我也有导入路由器测试模块。我也需要导入正在测试的服务,并且我导入了SessionService,因为我也在我的服务中使用它。

    之后,我通过TestBed.get获取这些依赖项的服务实例:这将允许我监视它们的属性,查看它们的值以及它们是否被调用。这也将允许我调用我想要测试的函数。

    1. 第一个测试来了。第一个非常简单,默认实现:我测试是否正确创建了服务。如果不是,则意味着您缺少依赖项,或者您错误地模拟了您的依赖项。 expect(service).toBeTruthy() 是真正的测试:期待,茉莉期待(duh)服务是真实的(即它不应该等于未定义或空)。

    2. 下一个测试是真正的测试:在这个测试期间,我希望我的函数调用某个端点,带有某个动词。

    我首先说,一旦打了电话,我就必须结束测试。这是通过调用done 来完成的,这是上面给出的回调。

    然后,我模拟一个 HTTP 调用:这意味着我让 Angular 相信我正在进行一个 HTTP 调用,但我实际上是假装的。但在它眼里,这就像制作一个真实的:这就是模拟:你模拟一种行为或一种价值。如您所见,我在特定端点上模拟了一个 HTTP 调用,并且我期望一个特定的 HTTP 动词。

    最后,如果我的函数getConfigurationsGETS 在那个 URL 上,我的测试将成功,如果没有,它将失败。这意味着,如果我在实际服务中更改端点,测试将失败:此测试可防止副作用

    其他测试与此相同,所以我想我不需要解释它们。

    如果您想知道,我不是一个人做的,就像您一样,我寻求帮助并遵循教程。但是一旦你习惯了它,测试你的功能就会变得非常快速和容易。

    我希望这会有所帮助,如果您希望我解释任何内容,请随时提出!

    【讨论】:

    • trichetriche 非常感谢您详细解释每一行。这正是我想要的。非常感谢您的时间和分享您的知识。我已经发布了这个问题stackoverflow.com/questions/49940214/…。我对我们调用服务的实际功能然后调用实际 url 的一件事感到困惑,当我断言服务返回的实际数据时,创建模拟数据的用途是什么?例如:正如我在帖子中所做的那样:expect(cmets).toBe('json');期望(cmets).toContain('cmets');.
    • mockhttp(HttpClientTestingModule, HttpTestingController) 这里有什么用?抱歉,但我觉得我们可以在不使用此模块的情况下纠正相同的逻辑。
    • 那我来回答吧!
    • 那会很棒。谢谢!
    【解决方案2】:

    模拟服务的想法是您不需要使用实际功能(真正的 http 调用),但您确实需要服务方法来运行您的代码,没有任何异常。

    例如: 您有一个组件,它在某些时候通过 Http 从某些 API 收集数据。 其中一个单元测试应该测试您的组件是否进行了该调用,但是如果那里有真正的调用,您就不必在乎了。单元测试的重点是测试调用是否发生。

    编辑: 如果某些内部逻辑进行调用以收集数据以显示某些内容,也会发生同样的情况。您应该模拟 http 调用并返回您自己的数据。你的单元测试不应该依赖外部的东西。想象一下,您的测试将在没有互联网的环境中运行。测试应该随时通过。

    无论您测试何种服务,都适用这种情况。单元测试应该有单一的责任。他们不应该依赖任何不同于其主要目的的东西。

    【讨论】:

      【解决方案3】:

      实际上,我在过去一周才遇到这个问题,所以这对我来说很新鲜。 Jasmine 也让我有些困惑,所以我可以分享我为解决我的问题所做的工作。

      首先,Angular 教程会误导新测试人员。它声称我们应该使用 Jasmine,但随后开始使用 TestBed,这有点误导。我最终选择了 TestBed,我可以向你展示我使用了什么。

      所以你有你的描述:

      descripe('randomService', () -> {
      
      }
      

      你需要初始化你的`randomService':

      descripe('randomService', () -> {
          let randomService: RandomService;         
      }
      

      使用 beforeEach(),您可以在 describe 中的每个 it 语句之前重新初始化、赋值等。

      descripe('randomService', () -> {
         let randomService: RandomService;         
         beforeEach(() => {
             imports: [
                 HttpClientTestingModule
             ]
         });
      }
      

      所以我告诉 Angular 在每个 it 块之前重新导入 HttpClientTestingModule。我的randomService 需要HttpClient,所以我需要创建一个Jasmine Spy 对象,它会返回我告诉它的内容,而不是让我的randomService 访问实际后端并更改后端中的真实数据。

      descripe('randomService', () -> {
         let randomService: RandomService;
         httpClientSpy;
      
         beforeEach(() => {
             imports: [
                 HttpClientTestingModule
             ]
         });
         httpClientSpy = jasmine.CreateSpyObj('Http', ['get']);
         randomService = new RandomService(<any> httpclientSpy);       
      }
      

      所以现在每当我在我的 randomService 中执行“get”方法时,它实际上会使用 httpClientSpy,并且它会编译,因为我告诉 randomService 我的参数是“any”类型并且据其所知,即实际上是一个真正的 HttpClient,即使它不是。要正确使用它,您必须为您的虚假获取设置虚假退货:

      descripe('randomService', () -> {
         let randomService: RandomService;
         httpClientSpy;
         mockResponse = { 1: ['first', 'second', 'third'] };
         beforeEach(() => {
             imports: [
                 HttpClientTestingModule
             ]
         });
         httpClientSpy = jasmine.CreateSpyObj('Http', ['get']);
         randomService = new RandomService(<any> httpclientSpy);       
      });
      
          it('should return first, second, third', () => {
              spyOn(httpClientSpy, 'get').and.returnValue(Observable.of(mockResponse));
              // randomService. <your get method here...>
              randomService.getValue().subscribe((response) =>
                  expect(resposne[0].length).toEqual(3);
      };
      });
      

      该响应应该是在我们的 beforeEach() 中创建的 mockResponse,它不必在 beforeEach() 中,但在这个示例中,我将它留在了那里。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-03-31
        • 1970-01-01
        • 1970-01-01
        • 2017-04-04
        相关资源
        最近更新 更多