如何在 jest / apollo 客户端中捕获被拒绝的 graphql 订阅?

Posted

技术标签:

【中文标题】如何在 jest / apollo 客户端中捕获被拒绝的 graphql 订阅?【英文标题】:How to catch rejected graphql subscription in jest / apollo client? 【发布时间】:2018-12-10 23:07:23 【问题描述】:

我正在尝试使用 graphql-yoga 编写笑话测试来涵盖订阅。

我能够成功地测试快乐路径是订阅作品(使用身份验证)。不幸的是,我无法测试订阅 websocket 连接被拒绝的情况。

在我的服务器设置中,我拒绝任何未通过我的身份验证条件的 websocket 连接:

const app = await server.start(
    cors,
    port: process.env.NODE_ENV === "test" ? 0 : 4000,
    subscriptions: 
      path: "/",
      onConnect: async (connectionParams: any) => 
        const token = connectionParams.token;
        if (!token) 
          throw new AssertionError( message: "NO TOKEN PRESENT" );
        
        const decoded = parseToken(token, process.env.JWT_SECRET as string);
        const user = await validateTokenVersion(decoded, redis);
        if (user === ) 
          throw new AssertionError( message: "NO VALID USER" );
        
        return  user ;
      
    
  );

(https://github.com/jakelowen/typescript-graphql-boilerplate-server/blob/master/src/startServer.ts#L87)

现在在我的相关测试中:https://github.com/jakelowen/typescript-graphql-boilerplate-server/blob/master/src/modules/counter/counter.test.ts

第一个测试(幸福的道路)如我所愿通过:

// works as expected.
  test("should start a subscription on network interface and unsubscribe", async done => 
    const client = new TestClientApollo(process.env.TEST_HOST as string);
    await client.register(email, password);
    await User.update( email ,  confirmed: true );
    await client.login(email, password);

    // set up subscription listener
    const sub = client.client.subscribe(defaultOptions).subscribe(
      next(result) 
        expect(result).toEqual(
          data: 
            counter: 
              count: 0
            
          
        );
        sub.unsubscribe();
        done();
      
    );
  );

然后我尝试了 3 种不同的方法来捕捉我期望在未经身份验证的场景中看到的期望。这些测试都没有像我希望的那样通过:

// Does not work! I am expecting an error.
  test("Unauthed subscriptions are rejected", done => 
    const client = new TestClientApollo(process.env.TEST_HOST as string);

    const sub = client.client.subscribe(defaultOptions).subscribe(
      next(result) 
        expect(result).toEqual(
          data: 
            counter: 
              count: 0
            
          
        );
        sub.unsubscribe();
        done();
      
    );

    // Received value must be a function, but instead "object" was found
    expect(sub).toThrow();
  );

  // does not work
  // Error: Uncaught  message: 'NO TOKEN PRESENT' 
  test("Unauthed subscriptions are rejected second attempt", done => 
    const client = new TestClientApollo(process.env.TEST_HOST as string);

    try 
      const sub = client.client.subscribe(defaultOptions).subscribe(
        next(result) 
          expect(result).toEqual(
            data: 
              counter: 
                count: 0
              
            
          );
          sub.unsubscribe();
          // done();
        
      );
     catch (error) 
      console.log(error);
      expect(error).toEqual(
        message: "NO TOKEN PRESENT"
      );
      done();
    
  );

  // does not work
  // Error: Uncaught  message: 'NO TOKEN PRESENT' 
  test("Unauthed subscriptions are rejected second attempt", done => 
    const client = new TestClientApollo(process.env.TEST_HOST as string);

    try 
      const sub = client.client.subscribe(defaultOptions).subscribe(
        next(result) 
          expect(result).toEqual(
            data: 
              counter: 
                count: 0
              
            
          );
          sub.unsubscribe();
          // done();
        
      );
     catch (error) 
      console.log(error);
      expect(error).toEqual(
        message: "NO TOKEN PRESENT"
      );
      done();
    
  );

  // does not work
  // Expected the function to throw an error.
  // But it didn't throw anything.
  test("Unauthed subscriptions are rejected third attempt", done => 
    const client = new TestClientApollo(process.env.TEST_HOST as string);

    expect(async () => 
      const sub = await client.client.subscribe(defaultOptions).subscribe(
        next(result) 
          expect(result).toEqual(
            data: 
              counter: 
                count: 0
              
            
          );
          sub.unsubscribe();
          done();
        
      );
    ).toThrowError();
  );

  // does not work
  // Expected the function to throw an error.
  // But it didn't throw anything.
  test("Unauthed subscriptions are rejected fourth attempt", done => 
    const client = new TestClientApollo(process.env.TEST_HOST as string);

    const attempt = async () => 
      const sub = await client.client.subscribe(defaultOptions).subscribe(
        next(result) 
          expect(result).toEqual(
            data: 
              counter: 
                count: 0
              
            
          );
          sub.unsubscribe();
          done();
        
      );
    ;

    expect(attempt).toThrowError();
  );

知道如何在未经身份验证的场景的测试中预期我所期望的断言错误吗?

完整的回购:https://github.com/jakelowen/typescript-graphql-boilerplate-server

【问题讨论】:

【参考方案1】:

嘘。我阅读了一般的 observables 和 observable.subscribe() ,发现第二个可选参数是一个 onError 回调函数。将测试重构为:

test("Unauthed subscriptions are rejected", done => 
    const client = new TestClientApollo(process.env.TEST_HOST as string);
    // jest.setTimeout(1000); // increase timeout
    client.client.subscribe(defaultOptions).subscribe(
      res => 
        console.log(res);
      ,
      err => 
        expect(err).toEqual( message: "NO TOKEN PRESENT" );
        done();
      
    );
  );

一切都按预期工作。万岁!

副社论:让我有点惊讶的是,经过数十小时的 google、*** 和 github 搜索,我从未找到一个关于如何正确测试 graphql 订阅的简单、清晰的教程。 em>

【讨论】:

“从未找到关于如何正确测试 graphql 订阅的简单、清晰的教程”——截至 2019/06 仍然如此。找到您的问题,因为官方文档中没有提及订阅单元测试。

以上是关于如何在 jest / apollo 客户端中捕获被拒绝的 graphql 订阅?的主要内容,如果未能解决你的问题,请参考以下文章

在 Vue 3 中使用 Jest 测试 Apollo 客户端 API(组合 API)

如何从 graphql apollo 客户端查询中获取/记录/捕获错误

使用 apollo 在 Angular 中捕获 http 状态代码

如何在 vuejs 中使用 jest 捕获外部库的元素?

如果令牌过期,如何在 apollo 客户端中重定向或导航

错误边界不会从 apollo 客户端捕获错误