简体   繁体   English

Jasmine / Karma测试Angular 5 HTTP响应

[英]Jasmine/Karma testing Angular 5 HTTP responses

I'm totally new to TDD and I'm trying to debug a big Angular 5 application we're working in, at the company. 我对TDD完全陌生,我正在尝试调试公司中正在使用的Angular 5大应用程序。

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");

    })
  })
});

I want you to focus in one part: the DataRequester service. 我希望您专注于一部分:DataRequester服务。 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... 我只想在这里检查此响应对象是否包含属性“ panel”以及测试...

...¡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. 但是,如果我尝试将属性的名称更改为某些不存在的属性,它也将验证为true。 So maybe, the HTTP request is NOT working properly here, doing something wrong here. 因此,也许HTTP请求在这里不能正常工作,在这里做错了。

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? 为什么DataRequester的“ requestData”方法不能正确执行,所以Jasmine能够在响应对象中正确测试我想要的条件?

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 . 首先,你必须了解单元测试的基础知识:单元测试旨在防止在一个单位 的副作用

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. 副作用是所需行为的变化:例如,您要使div的颜色为蓝色,并且在进行一些代码编辑后,将其着色为红色:这是一个副作用。

A unit is the feature you're testing. 单位是您要测试的功能。 In Angular, you can see which one it is with this : 在Angular中,您可以看到以下内容:

describe('AppComponent'

Here, you're testing AppComponent . 在这里,您正在测试AppComponent

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. 这意味着您不再没有一个单元:您正在测试多个单元。

To correct that 更正

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). 您将检查组件是否实际上在调用服务,而不是服务是否在调用API(这将由服务本身的单元测试进行检查)。

In your test bed : 在您的测试床上:

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 ). 通常,组件仅处理视图:您实际上并不能模拟它们(尽管您应该 )。

This allows you to remove the HttpModule , which isn't required in any test . 这允许您删除HttpModule ,这在任何测试中都是不需要的。

You can also remove your routing module, because Angular provides a mock of it already : RouterTestingModule . 您还可以删除路由模块,因为Angular已经提供了它的模拟: RouterTestingModule

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 : {}
    },
    {
      provide: DataRequester,
      useValue: {}
    },
    { provide: APP_BASE_HREF, useValue : '/' }
  ]

Now you have a proper test bed. 现在,您已经有了合适的测试床。

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 . 您所要做的就是,在useValue中添加useValue并使用正确的签名在组件中使用的每个服务属性

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) (模拟的值并不重要,您可以根据需要更改它们)

As you can see, because your requester service returns an Observable, you are forced to return one too. 如您所见,由于您的请求者服务返回了一个Observable,因此您也不得不返回一个。 And because your reactive service stores the width, you have to declare a variable of type number. 并且由于您的反应式服务存储了宽度,因此您必须声明一个类型为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);
}));

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). 您声明所做的事情(测试驱动),监视服务(以查看是否已被调用),然后进行调用(因为我们的模拟已经是一个Observable并返回100)。

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). 然后,调用该方法进行测试,然后刷新异步调用( fakeAsynctick是Angular的一部分,您可以在有关测试的文档中找到它们)。

Finally, you make your expectations. 最后,您会做出期望。

And with that, you successfully tested your first method ! 这样,您就成功地测试了第一种方法!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM