【问题标题】:How can I test non-mocked promises in Jasmine?如何在 Jasmine 中测试非模拟承诺?
【发布时间】:2020-12-23 20:09:07
【问题描述】:

背景

我有一个 Angular 项目,它有一个用于后端 Web 请求的 Web API,但现在我们需要转换到不同的 API 提供程序。

API 请求都集成到 Angular 服务代码文件中,每个服务的存在都是为了履行特定的职责。

我和我的开发人员同事的任务是创建新的“版本 2”服务,使用相同的方法签名和参数,他们必须调用第二个新 API。

目标是创建具有相同方法和签名的新服务,采用完全相同的参数并产生相同的结果。但是,服务器上的 API 方法签名通常略有不同,其中一些来自新 API 的结果可能需要修改和调整以适应原始格式。

设置

为了帮助实现这一过渡,我们希望创建稍微不那么传统的 Jasmine 单元测试。

对于每个正在转换的服务,我们打算创建一个 ...spec.ts 文件,该文件将执行以下测试和调试的高级要求。

  1. 初始化测试并使用TestBed.configureTestingModule,为旧服务和新服务设置适当的提供程序。
  2. 对于从服务器返回结果的服务上的每个方法,运行it(...) 测试。
  3. 在测试中,首先调用MyService.apiRequest()。获取 observable 并获取结果。
  4. 调用 expect(...) 旧结果集以进行完整性检查。
  5. 致电MyServiceV2.apiRequest()。获取 observable 并获取结果。
  6. expect(...) 新结果集是真实的。
  7. 构造 expect(...) 语句,仔细检查旧 API 的结果是否与新 API 结果匹配。

需要明确的是,这些不是我们打算在 CI/CD 管道中使用的测试,也不是为了仔细检查代码。这些是 Jasmine 测试,我们用于快速检查来自我们必须使用的各种 API 的结果集。

问题

我已经尝试过各种方法来设置这样的测试,但我似乎无法从我们的服务中获取结果,等待观察结果并比较数据。我发现的很多 Jasmine 文档都希望您在使用 observable 时通过模拟来伪造结果。这在我们的用例中不起作用。

在我们的例子中,我们的服务调用是合法的,并且是一种从两个特定 API 端点仔细检查我们的数据结果的方法。

为了全面披露,该应用程序目前停留在 Angular 8.2.14 和 Jasmine 2.8.0。下面列出了我为使其正常工作所做的各种尝试。

尝试 #1

it('should get data from MyService.getData(projectId)', ()=>{

        //oldSvc and newSvc are references to the old and new services, initialized in beforeEach(...)

        //Call to get the first round of data (old data.)
        oldSvc.getData(projectId).subscribe((oldData)=>{
            expect(oldData).toBeTruthy();
            newSvc.getData(projectId, false).subscribe((newData)=>{
                expect(newData).toBeTruthy();
                expect(newData).toEqual(oldData);
            });
        });
    });

这会返回 Jasmine 成功,但似乎这是因为“SPEC HAS NO EXPECTATION”。我猜测试在可观察订阅者有机会完成之前完成,更不用说调用expect(...) 方法对数据进行任何检查。因此,它看起来很成功,但实际上并没有做任何事情。

尝试 #2

Questions like this one 建议使用first() 方法等待并从每个异步请求中获取第一个结果。我不反对这种方法,但尽管我做出了一切努力,我还是得到了first() 不存在的错误。我添加了对first 的引用(see here 是一个关于如何执行此操作的 SO 问题),但这并不能解决问题。即使添加了导入,first() 也不存在,我得到构建错误,我不得不放弃这种方法。我什至试图用特殊的 TS 标志忽略错误,而不是得到构建错误,我从 Jasmine 中得到first() does not exist 相关错误。这是一个非首发。

尝试 #3

found this的文章中提到可以等待结果,使用jasmine.clock.tick(...)方法等待结果。通过将文章中的方法集成到您的测试文件中,您可以为您的可观察对象创建“同步”请求。伟大的! ...但这没有用。我的结果总是不确定的,证明它实际上并没有等待结果,即使时钟等待时间很长。

这是本文的重点和对该方法的示例调用。

//Method
function awaitStream(stream$: Observable<any>, skipTime?: number) {
  let response = null;
  stream$.subscribe(data => {
    response = data;
  });
  if (skipTime) {
    /**
     * use jasmine clock to artificially manipulate time-based web apis like setTimeout and setInterval
     * we can easily refactor this and use async/await but that means that we will have to actually wait out the time needed for every delay/mock request
     */
    jasmine.clock().tick(skipTime);
  }
  return response;
}

//Usage within an it(...) method

...
const oldData = awaitStream(oldSvc.getData(projectId), 10000);
//after calling the method, the result of the observable should be in oldData.  It isn't.
...

问题

这些是我为从 Jasmine 中的 API 调用获取异步数据以进行使用和测试所做的各种尝试。在我们当前的 Angular/Jasmine 版本限制下,我无法让这些工作。有没有人有一种直接的方法来调用和等待来自服务返回 Observables 的数据,并在 Jasmine 中检查结果?

谢谢。

【问题讨论】:

    标签: angular typescript unit-testing jasmine karma-jasmine


    【解决方案1】:

    这可能不会回答您的问题,但会引导您朝着正确的方向前进。

    对我来说奇怪的是,您似乎想要进行实际的 HTTP 调用(根据我收集的信息),不建议将其用于单元测试。但是我也明白你的观点,如果我们模拟 Http 调用或服务调用,那么测试就没有意义了。

    你可以尝试使用async/await来完成你想要的。

    import { take } from 'rxjs/operators';
    ....
    it('should get data from MyService.getData(projectId)', async ()=>{
    
            //oldSvc and newSvc are references to the old and new services, initialized in beforeEach(...)
            const oldData = await oldSvc.getData(projectId).pipe(take(1)).toPromise();
            expect(oldData).toBeTruthy();
            const newData = await newSvc.getData(projectId, false).pipe(take(1)).toPromise();
            expect(newData).toBeTruthy();
            expect(newData).toEqual(oldData); 
        });
    

    或者您可以在原始测试中使用 done 回调:

    it('should get data from MyService.getData(projectId)', (done)=>{
    
            //oldSvc and newSvc are references to the old and new services, initialized in beforeEach(...)
    
            //Call to get the first round of data (old data.)
            oldSvc.getData(projectId).subscribe((oldData)=>{
                expect(oldData).toBeTruthy();
                newSvc.getData(projectId, false).subscribe((newData)=>{
                    expect(newData).toBeTruthy();
                    expect(newData).toEqual(oldData);
                    done(); // finish test when done is called to ensure it goes through all assertions
                });
            });
        });
    

    话虽如此,我建议模拟您的 Http 响应,以免影响您的实际 API。看看这个article。您可以使用 HttpClientTestingModuleHttpTestingController 通过 url 或方法(GET、PUT、POST、DELETE)处理正在进行的 HTTP 请求,并为每个请求刷新特定的响应。

    =============编辑========

    第一个问题 - 像这样:

    let result: any; // you can change any to what you want
    beforeEach(async (done) => {
      result = await yourSvc.getData().pipe(take(1)).toPromise();
      done();
    });
    
    it('should do xyz', () => {
      console.log(result); // should have a handle on result now
    });
    

    第二个问题 - Done 不应该被导入。这是 Jasmine 的一个股票功能。基本上,我们可以称它为任何名称,但它必须是测试的第一个参数。

    it('should do xyz', (anyRandomNameYouWantHere: DoneFn) => {
      someAsyncTask.then(() => {
       // your assertions
       anyRandomNameYouWantHere(); // call the first argument to tell Jasmine you are done
      });
    });
    

    为了符合语义,您应该调用anyRandomNameYouWantHere as done 并且类型是DoneFn,但不必将类型放在那里。你可以了解更多关于donehere的信息。

    要获得first 运算符,您必须使用import { first } from 'rxjs/operators';,但如果您遇到错误,您应该检查tsconf.spec.json 以确保您拥有Jasmine 和其他模块的准确类型。检查空白项目的tsconf.spec.json 的外观,并将其与您的进行比较并进行一些更改。您可能需要重新启动 IDE。

    【讨论】:

    • 我原则上同意你的观点,但由于我们仅将这些测试用于开发中的调试,而不是实际的单元/集成测试,我们希望有这样的东西,我们可以迭代调用当我们对 Angular 应用程序或我们新的后端 API 进行更改时。我们将 Jasmine 用作一种工具,它并不打算用作它,但由于它内置在 Angular 中,它似乎是满足我们需求的潜在好选择。
    • 此外,这确实有效,但在一种情况下除外。我确实需要调用一个类似的服务,该服务在 beforeEach(...) 方法中返回一个 observable。不是同一个问题,但您知道如何做到这一点,并在测试中将结果发送到本地吗?
    • 抱歉,我知道我不应该这样做,但 rxjs 在 Jasmine 中似乎几乎没用。我找不到done() 方法,就像first() 和其他一些方法一样。每当我尝试通过正确的导入来引用它们时,该应用程序仍然会给我错误。
    • 我已尝试在我刚刚进行的编辑中回答您的问题。
    猜你喜欢
    • 1970-01-01
    • 2014-07-05
    • 1970-01-01
    • 1970-01-01
    • 2016-12-18
    • 2017-06-03
    • 2014-06-17
    • 2017-09-04
    • 1970-01-01
    相关资源
    最近更新 更多