Jasmine / Karma测试Angular 5 HTTP响应

Jasmine / Karma测试Angular 5 HTTP响应

问题描述:

我是TDD的新手,我正在尝试调试我们正在公司工作的一个大型Angular 5应用程序。

I'm totally new to TDD and I'm trying to debug a big Angular 5 application we're working in, at the company.

该应用程序运行良好,但现在我必须实施测试,而我正在学习这些东西,同时我创建了最基本的和初学者。我已经为主模块编写了这个东西,只是为了尝试这个工具:

The app is working well, but now I have to implement tests, and I'm learning this stuff while I create the most basic and starter ones. I wrote this stuff already for the main module, just for trying this tooling:

describe('AppComponent', () => {
  let httpClientSpy: { get: jasmine.Spy }
  let dataReq: DataRequester;
  let queryBuilder: PanelQueryBuilder;
  let query: DataQuery;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        MainComponent,
        Menu,
        MessageViewer
      ],
      imports: [
        BrowserModule,
        BrowserAnimationsModule,
        routing,
        AngularFontAwesomeModule,
        FormsModule,
        HttpModule,
        ChartModule,
        Ng4LoadingSpinnerModule.forRoot(),
        NgbModule.forRoot()
      ],
      providers: [
        ReactiveService,
        DataRequester,
        { provide: APP_BASE_HREF, useValue : '/' }
      ]
    }).compileComponents();
  }));
  it('should create the app', async(() => {
    const fixture = TestBed.createComponent(MainComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();

  }));
  it('userType should be defined', async(()=>{
    expect(MainComponent.userType).toBeDefined();
  }))

  it('DataRequester exists and retrieves info', async(()=>{


    beforeEach(() => {
      // TODO: spy on other methods too
      httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']);
      dataReq = new DataRequester(<any> httpClientSpy);
      queryBuilder = new PanelQueryBuilder();
    });



    expect(MainComponent).toBeDefined();
  }))
  it('1. Build query and check integrity', async()=>{
    query = queryBuilder.buildInitialQuery("panel", "conversions", 144);
    expect(query).toBeDefined();
  })

  it('2. Send query and check result', async()=>{
    dataReq.requestData(query, 'conversions').then( res => {
      expect(res).toContain("panel");

    })
  })
});

我希望您专注于一个部分:DataRequester服务。它是一个服务,它有一个返回promise的方法,并调用我们后端的特定部分,返回数据。我只想在这里检查这个响应对象是否包含属性panel,并且测试......

I want you to focus in one part: the DataRequester service. It's a service that has a method which returns a promise, and calls a specific part of our backend, returning the data. I just want here to check if this response object contains the property "panel", and the test...

...¡实际上它说它存在!但是,如果我尝试将属性的名称更改为某些不存在的属性......它也会验证为true。所以,也许,HTTP请求在这里工作不正常,在这里做错了。

...¡actually says it exists! But if I try to change the name of the property, to some non-existing property... it validates as true too. So maybe, the HTTP request is NOT working properly here, doing something wrong here.

我在这段代码中做了什么不好的事情?为什么DataRequesterrequestData方法没有正确执行,所以Jasmine能够在响应对象中正确测试我想要的条件?

Am I doing something bad in this code? Why doesn't the DataRequester "requestData" method execute properly, so Jasmine is able to properly test the conditions I want, in the response object?

是的,你在代码中做了一些不好的事情。但是别担心,我在开始的时候做了同样的事情。

Yes, you"re doign something bad in your code. But don't worry, I did the same thing when I started.

首先,你必须了解单元测试的基础知识:进行单元测试以防止单位中的副作用

First, you must understand the basics of unit testing : unit tests are made to prevent side effects in a unit.

副作用是想要行为的变化:例如,你想用蓝色对div进行着色,经过一些代码编辑后,它会变成红色:这是副作用。

Side effects are changes in a wanted behavior : for instance, you want to color a div in blue, and after some code editing, it colors red : this is a side effect.

单位是你正在测试的功能。在Angular中,你可以看到它是哪一个:

A unit is the feature you're testing. In Angular, you can see which one it is with this :

describe('AppComponent'

在这里,你正在测试 AppComponent

现在我们解决了这个问题,让我们来看看你的测试中出了什么问题ts:您使用实际的服务实例。这意味着您不再需要一个单元:您正在测试多个单元。

Now that we settled this, let's go over what is wrong in your tests : you use real instances of your services. This means that you don't don't a single unit anymore : you're testing several units.

您必须模拟您的服务。您将检查您的组件是否实际正在调用该服务,而不是该服务是否正在调用您的API(这将通过服务本身的单元测试来检查)。

You have to mock your services. You will check if your component is actually calling the service, and not if the service is calling your API (this will be checked by the unit test of the service itself).

在你的测试床上:

TestBed.configureTestingModule({
  declarations: [
    MainComponent,
    Menu,
    MessageViewer
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    routing,
    AngularFontAwesomeModule,
    FormsModule,
    HttpModule,
    ChartModule,
    Ng4LoadingSpinnerModule.forRoot(),
    NgbModule.forRoot()
  ],
  providers: [
    {
      provide: ReactiveService,
      useValue : {}
    },
    {
      provide: DataRequester,
      useValue: {}
    },
    { provide: APP_BASE_HREF, useValue : '/' }
  ]

通常,组件只处理视图:你不是真的要模仿它们(尽管你应该)。

Usually, components handle only the view : you don't really to mock them (although you should).

这允许你删除 HttpModule ,这在任何测试中都不是

This allows you to remove the HttpModule, which isn't required in any test.

您也可以删除路由模块,因为Angular已经提供了一个模拟它: RouterTestingModule

You can also remove your routing module, because Angular provides a mock of it already : RouterTestingModule.

你的试验台变成了这个

TestBed.configureTestingModule({
  declarations: [
    MainComponent,
    Menu,
    MessageViewer
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    RouterTestingModule,
    AngularFontAwesomeModule,
    FormsModule,
    ChartModule,
    Ng4LoadingSpinnerModule.forRoot(),
    NgbModule.forRoot()
  ],
  providers: [
    {
      provide: ReactiveService,
      useValue : {}
    },
    {
      provide: DataRequester,
      useValue: {}
    },
    { provide: APP_BASE_HREF, useValue : '/' }
  ]

现在你有一张合适的试验床。

Now you have a proper test bed.

你需要做的就是加上 useValue 你的模拟,你的组件使用的每个服务属性都有正确的签名

All you have to do left, is add in the useValue of your mocks, every service property used by your component with the correct signature.

For ins tance,想象你的app组件有这个:

For instance, imagine your app component has this :

ngOnInit() {
  this.dataRequester.requestWidth('URL').subscribe(res => this.reactiveService.width = res);
}

然后你的试验床就变成了这样:

Then your test bed becomes this :

TestBed.configureTestingModule({
  declarations: [
    MainComponent,
    Menu,
    MessageViewer
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    RouterTestingModule,
    AngularFontAwesomeModule,
    FormsModule,
    ChartModule,
    Ng4LoadingSpinnerModule.forRoot(),
    NgbModule.forRoot()
  ],
  providers: [
    {
      provide: ReactiveService,
      useValue : {
        width: 0
      }
    },
    {
      provide: DataRequester,
      useValue: {
        requestWidth: () => of(100)
      }
    },
    { provide: APP_BASE_HREF, useValue : '/' }
  ]

(模拟的值不重要,你可以根据需要更改它们)

(Values of the mock aren't important, you'll change them on demand)

如你所见,因为你的要求ter服务返回一个Observable,你也*返回一个。并且因为您的被动服务存储宽度,您必须声明一个类型为number的变量。

As you can see, because your requester service returns an Observable, you are forced to return one too. And because your reactive service stores the width, you have to declare a variable of type number.

现在,在您的测试中,使用前面的示例,您将执行此操作:

Now, in your test, using the previous example, you will do this :

it(`should get width from requester and store it in reactive service`, fakeAsync(() => {
  spyOn(component['dataRequester'], 'requestWidth').and.callThrough();

  component.ngOnInit();
  tick();

  expect(component['dataRequester'].requestWidth).toHaveBeenCalledWith('URL');
  expect(component['reactiveService'].width).toEqual(100);
}));

你宣布你做了什么(测试驱动),你监视你的服务(看它是否有被调用),然后你调用(因为我们的mock已经是一个Observable并返回100)。

You declare what you do (test driven), you spy on your service (to see if it has been called), then you call through (because our mock is already an Observable and returns 100).

然后,你调用方法来测试,并刷新异步调用out( fakeAsync tick 是Angular的一部分,你可以在关于测试的文档中找到它们)。

Then, you call the method to test, and flush the async calls out (fakeAsync and tick are part of Angular, you can find them in the docs about testing).

最后,你要做出你的期望。

Finally, you make your expectations.

然后,您成功测试了第一种方法!

And with that, you successfully tested your first method !