【问题标题】:Trying to run Angular HttpClient Jasmine test against live REST API: nothing happens尝试针对实时 REST API 运行 Angular HttpClient Jasmine 测试:没有任何反应
【发布时间】:2019-12-05 22:41:10
【问题描述】:

我有一个用 .Net Core 编写的简单“联系人”REST 服务。它工作正常。

我正在尝试编写一个 Angular 8.3 客户端与它对话。

我做的前两件事是:

  1. 创建 Contact.ts 和 Note.ts(对应 REST 模型)
  2. 创建一个 Angular 服务以与 .Net Core REST API 进行通信。

认为也许测试服务的最佳方式是使用自动生成的单元测试,contacts.service.spec.ts。我确实想使用模拟服务:我想使用“实时”HttpClient,以便我的 Angular“联系人”服务可以直接与 .Net Core API 对话。

它不起作用:没有错误,没有警告:测试只是“通过”,甚至没有尝试发送 HTTP 消息或等待 HTTP 响应。

问:如何针对实时 REST 服务调试我的 Angular 服务?

问:我可以使用contacts.service.spec.ts Jasmine 测试/Karma 运行器,还是应该做“其他事情”来逐步执行代码?

提前谢谢你!

models/contact.ts:

export class Contact {
  ContactId?: number;
  Name: string;
  EMail: string;
  Phone1: string;
  Phone2: string;
  Address1: string;
  Address2: string;
  City: string;
  State: string;
  Zip: string; 
}

models/note.ts

export class Note {
  NoteId?: number;
  Text: string;
  Date: Date;
  ContactId?: number;
}

services/contacts.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { retry, catchError } from 'rxjs/operators';
import { environment } from 'src/environments/environment';

import { Contact } from '../models/Contact';
import { Note } from '../models/Note';


@Injectable({
  providedIn: 'root'
})
export class ContactsService {

  myAppUrl: string;
  myApiUrl: string;
  httpOptions = {
    headers: new HttpHeaders({
      'Content-Type': 'application/json; charset=utf-8'
    })
  };

  constructor(private http: HttpClient) {
    this.myAppUrl = 'http://localhost:53561/';  // environment.appUrl;
    this.myApiUrl = 'api/Contacts/';
  }

  getContacts(): Observable<Contact[]> {
    const url = this.myAppUrl + this.myApiUrl;
    return this.http.get<Contact[]>(url)
    .pipe(
      retry(1)
    );
  }
}

services/contacts.service.spec.ts

import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { ContactsService } from './contacts.service';

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

  it('should be created', () => {
    const service: ContactsService = TestBed.get(ContactsService);
    expect(service).toBeTruthy();
  });

  it('should retrieve all contacts', () => {
    const contactsService: ContactsService = TestBed.get(ContactsService);
    let observable = contactsService.getContacts();
    expect(observable).toBeTruthy();
    debugger;
    observable.subscribe(data => {
      debugger;
      console.log("Done");
    },
    error => {
      debugger;
      console.error("observable error");
    });
  });
});

ng 测试


我尝试将done() 添加到我的服务测试中,并尝试在单元测试中实例化 HttpClient。它仍然没有对 REST 服务器进行任何 HTTP 调用:(

import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { HttpClient } from '@angular/common/http';
import { ContactsService } from './contacts.service';

describe('ContactsService', () => {
  let httpClient: HttpClient;
  let service: ContactsService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [ContactsService, HttpClient]
    });
    // ERROR:
    //   "Timeout - Async callback was not invoked within 5000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)
    // Tried changing to 20000 - still getting Timeout...
    let originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
    jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000; // Getting timeout @default 5000
    httpClient = TestBed.get(HttpClient);
    //service = TestBed.get(ContactsService);
    service = new ContactsService(httpClient);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should retrieve all contacts', (done: DoneFn) => {
      service.getContacts().subscribe(data => {
        done();
    });
  });
});

当前错误(尽管在测试中手动更新超时值)

Error: Timeout - Async callback was not invoked within 20000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)
Error: Timeout - Async callback was not invoked within 20000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)
    at <Jasmine>

感谢 Pytth 和 wessam yaacob。以下是我的工作方式:

  1. 在 .Net Core REST 服务上配置 CORS

    public class Startup
      ...
      public void ConfigureServices(IServiceCollection services)
        ...
        services.AddCors(options => {
          options.AddPolicy("CorsPolicy",
            builder => builder.AllowAnyOrigin()
           .AllowAnyMethod()
           .AllowAnyHeader());
        });
      ...
      public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        ...
        app.UseCors("CorsPolicy");
    

    我已经在做这件事了 - 但在这里注意一下很有用

  2. 使用 HttpClientModule 和 HttpClient 代替 HttpClientTestingModule 和 HttpTestingController

    我从不想要使用“HttpClientTestingModule”,因为我想与实时服务交谈 - 而不是进行模拟调用。

    我在此过程中大幅更改了我的代码,但这是我最终完成的单元测试:

    import { TestBed } from '@angular/core/testing';
    import { HttpClientModule, HttpClient, HttpErrorResponse } from '@angular/common/http';
    
    import { BlogPostService } from './blog-post.service';
    
    describe('BlogPostService', () => {
      let httpClient: HttpClient;
      let service: BlogPostService;
    
      beforeEach(() => {
        TestBed.configureTestingModule({
          imports: [HttpClientModule],
        });
        httpClient = TestBed.get(HttpClient);
        service = TestBed.get(BlogPostService);
      });
    
      it('should be created', () => {
        expect(service).toBeTruthy();
      });
    
      it('should retrieve blog posts', (done: DoneFn) => {
        service.getBlogPosts().subscribe(data => {
          done();
        });
      });
    });
    
  3. 最后说明:

    CORS NOT 似乎可以工作除非我使用了 HTTPS:

    export class BlogPostService {
      ...
      constructor(private http: HttpClient) {
          this.myAppUrl = 'https://localhost:44330/'  // CORS error if 'http://localhost:44330/'
          this.myApiUrl = 'api/BlogPosts/';
          ...
    

PS:我完全了解“单元测试”和“集成测试”之间的“学术区别”。我只是希望 Angular“单元测试”框架能给我带来与在 Java 世界中使用 static void main (String[] args) 代码相同的便利。

事实证明这绝对是不是的情况......

我还没有尝试过针对这种情况的 e2e 测试 - 只创建一个虚拟页面(一个简单的组件)并将其用于测试会更容易...

【问题讨论】:

    标签: angular jasmine angular-httpclient


    【解决方案1】:

    如果您要针对真正的 rest api 测试您的服务,那么您必须将 HttpClientTestingModule 替换为 HttpClientModule

    HttpClientTestingModule 仅用于模拟

    同样在您的设置文件中,您必须在接受的来源中添加测试域 url 以避免 ->> CORS 策略:No 'Access-Control-Allow-Origin'

    在你的情况下http://localhost:9876

       public void Configure(IApplicationBuilder app, IHostingEnvironment env)
            { 
                  ......
                  app.UseCors(options =>
                  options.WithOrigins("http://localhost:9876") 
                  ......
            }
    

    【讨论】:

    • 很好的建议 - 谢谢!我会告诉你它是否有效!
    • BINGO - 成功了!谢谢!注意:我发现的另一件事是 CORS 似乎不适用于 .Net Core 3.x,除非您使用 https
    【解决方案2】:

    您需要使用 jasmine 提供的done。将编辑更新:

    https://codecraft.tv/courses/angular/unit-testing/asynchronous/#_jasmines_code_done_code_function


    对于您的更新:这应该会让您朝着正确的方向前进

    import { HttpClientTestingModule } from '@angular/common/http/testing';
    import { HttpClient, HttpHeaders } from '@angular/common/http';
    
    describe('LoginRepository', () => {
      let httpClient: HttpClient;
    
      beforeEach(() => {
        TestBed.configureTestingModule({
          imports: [HttpClientTestingModule]
        });
        service = TestBed.get(LoginRepository);
        httpClient = TestBed.get(HttpClient);
      });
    
        describe('#login', () => {
            it('makes http request', () => {
             spyOn(httpClient, 'post');
          //...
    

    【讨论】:

    • 谢谢。我试图添加“完成()”......但现在我得到了NullInjectorError: StaticInjectorError(DynamicTestModule)[HttpClient]:...。请在上面查看我的更新。
    • 看起来是一个你没有正确引导某些东西的问题。检查并确保您在任何正在测试的模块中正确导入 HttpClient。请看我的更新。但这几乎可以肯定是配置问题。
    • 是的,我解决了“引导”问题。现在我面临一个“异步超时”问题。在我有机会更新我的帖子之前,你更新了你的帖子——我现在要试试你的建议。谢谢你:)
    • 还是不行。我在网上找到的每个博客都在讨论如何模拟 REST 调用。我不想想要“模拟”它——我想INVOKE它。也许这在 Jasmine 单元测试中是不可行的。我认为接下来我要尝试的是创建一个带有按钮的虚拟组件:并尝试以这种方式调用“实时”服务......
    • 我真的很好奇,你为什么要在单元测试中实际调用正在运行的服务器?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-21
    • 1970-01-01
    • 2020-01-06
    • 1970-01-01
    • 2014-12-09
    • 2012-02-05
    • 2018-11-14
    相关资源
    最近更新 更多