【问题标题】:Testing Observables with jest用玩笑测试 Observables
【发布时间】:2018-06-05 10:58:33
【问题描述】:

如何使用 Jest 测试 Observables?

我有一个每秒触发一次的 Observable,我想在 jest 超时之前测试第一个事件是否正确触发。

const myObservable = timer(0, 1000); // Example here

it('should fire', () => {
  const event = myObservable.subscribe(data => {
    expect(data).toBe(0);
  });
});

这个测试通过了,但是如果我用toBe('anything')替换它也通过了,所以我想我做错了什么。

我尝试使用 expect.assertions(1),但它似乎只适用于 Promises。

【问题讨论】:

  • 对于异步测试,您可以传递可选的done 参数it('should fire', done => {...}),当测试完成时您调用自己subscribe(data => done())
  • 是的,有效。谢谢!

标签: rxjs observable jestjs


【解决方案1】:

在 Jest 文档中有一些关于为测试传递参数的好例子。可以调用此参数来表示测试通过,或者您可以对其调用 fail 以使测试失败,或者它可以超时并失败。

https://jestjs.io/docs/en/asynchronous.html

https://alligator.io/testing/asynchronous-testing-jest/

示例

请注意,我将超时设置为 1500 毫秒

const myObservable = timer(0, 1000); // Example here

it('should fire', done => {
  myObservable.subscribe(data => {
    done();
  });
}, 1500); // Give 1500ms until it fails

使用 setTimeout

查看是否失败的另一种方法
const myObservable = timer(0, 1000); // Example here

it('should fire', done => {
  myObservable.subscribe(data => {
    done();
  });

  // Fail after 1500ms
  setTimeout(() => { done.fail(); }, 1500);
}, timeToFail);

【讨论】:

  • Observables 和 async 是非常不同的概念。将可观察对象视为异步适用于简单的用例(如此处突出显示的用例)。一旦事情变得复杂,您将很难让这些测试正常工作。这就是TestScheduler 存在的原因
【解决方案2】:

在没有虚假计时器和超时的情况下,我首选的测试 observable 的方法是 asyncawait 并使用 resolvesrejects 在预期的转换承诺上。

it('should do the job', async () => {
    await expect(myObservable
      .pipe(first())
      .toPromise())
      .resolves.toEqual(yourExpectation);
});

更新:

在 Rxjs 7 及更高版本中,您可以使用 lastValueFromfirstValueFrom 进行承诺转换

it('should do the job', async () => {
    await expect(lastValueFrom(myObservable))
      .resolves.toEqual(yourExpectation);
});

【讨论】:

    【解决方案3】:
    test('Test name', (done) => {
      service.getAsyncData().subscribe((asyncData)=>{
        expect(asyncData).toBeDefined();
           done();
        })
      });
    })
    

    【讨论】:

      【解决方案4】:

      从 6.2.1 版本开始,RxJS 支持 Jest 的假时间,因此您可以编写此测试的一种方法是:

      const myObservable = timer(0, 1000);
      
      jest.useFakeTimers('modern');
      
      it('should fire', () => {
        myObservable.subscribe(data => {
          expect(data).toBe(0);
        });
        jest.runAllTimers();
      });
      

      我发布了一个 library 来帮助进行此类测试(在下面的示例中,log 将日志记录添加到 observable,getMessages 检索已记录的消息):

      import { getMessages, log } from '1log';
      import { timer } from 'rxjs';
      
      const myObservable = timer(0, 1000);
      
      test('timer', () => {
        myObservable.pipe(log).subscribe();
        jest.runAllTimers();
        expect(getMessages()).toMatchInlineSnapshot(`
          [create 1] +0ms [Observable]
          [create 1] [subscribe 1] +0ms [Subscriber]
          [create 1] [subscribe 1] [next] +1.000s 0
          [create 1] [subscribe 1] [complete] +0ms
          · [create 1] [subscribe 1] [unsubscribe] +0ms
        `);
      });
      

      【讨论】:

        【解决方案5】:

        测试任何可观察到的 RXJS(是否开玩笑)的正确方法是在 rxjs/testing 中使用 TestScheduler

        例如:

        import { TestScheduler } from 'rxjs/testing';
        import { throttleTime } from 'rxjs/operators';
         
        const testScheduler = new TestScheduler((actual, expected) => {
          // asserting the two objects are equal - required
          // for TestScheduler assertions to work via your test framework
          // e.g. using chai.
          expect(actual).deep.equal(expected);
        });
         
        // This test runs synchronously.
        it('generates the stream correctly', () => {
          testScheduler.run((helpers) => {
            const { cold, time, expectObservable, expectSubscriptions } = helpers;
            const e1 = cold(' -a--b--c---|');
            const e1subs = '  ^----------!';
            const t = time('   ---|       '); // t = 3
            const expected = '-a-----c---|';
         
            expectObservable(e1.pipe(throttleTime(t))).toBe(expected);
            expectSubscriptions(e1.subscriptions).toBe(e1subs);
          });
        });
        

        来自RXJS marble testing testing docs

        如果你有一个简单的 observable,尝试将 observable 等转换为 Promise 效果很好。一旦事情变得更加复杂,你就会在不使用大理石图和正确的测试库的情况下挣扎。

        【讨论】:

          【解决方案6】:

          上面提到了两种方法

          1. 在我们的测试中完成参数并在我们测试后调用它。
          2. 使用 firstValueFrom(myObs) 或 lastValueFrom(myObs) 将我们的 observable 转换为 promise。并与他们一起使用 async await...

          如果我们要测试多个可观察对象,那么我们必须将可观察对象嵌套在我们的测试中,因为我们只能调用一次 done()。在这种情况下,异步等待方法可以派上用场。 在这个例子中,当我们调用 filter Customer 时,所有三个 observable 都会发出值,因此我们必须测试所有这些值。

          it('Filter Customers based on Producers- Valid Case Promise way ',async()=>{
              
              service.filterCustomers('Producer-1');
          
              await expect(firstValueFrom(service.customers$)).resolves.toEqual(['Customer-1']);
          
              await firstValueFrom(service.customers$).then((customers:string[])=>{
                expect(customers).toEqual(['Customer-1']);
                expect(customers.length).toBe(1);
              })
          
              await expect(firstValueFrom(service.products$)).resolves.toEqual([]);
              await expect(firstValueFrom(service.types$)).resolves.toEqual([]);
          
            }).
          

          【讨论】:

            猜你喜欢
            • 2021-02-10
            • 2020-07-31
            • 2018-01-29
            • 2019-02-22
            • 2020-11-14
            • 1970-01-01
            • 2017-11-29
            • 2020-07-04
            • 2020-06-30
            相关资源
            最近更新 更多