【问题标题】:Angular Unit Testing - How to test a service which creates a WebsocketAngular Unit Testing - 如何测试创建 Websocket 的服务
【发布时间】:2021-11-12 20:07:15
【问题描述】:

我需要为以下代码编写一个单元测试:

getSomething(id: string): Observable<State | null> {
  return new WebSocketSubject(webSocket(`ws://<websocket>`)
    .pipe(
      catchError(err => {
        console.error('Error in WebSocket connection: ', err);
        return concat(
          of(null),
          throwError(err)
        );
      }),
      retryWhen(errors =>
        errors.pipe(
          delay(1000)
        )
      )
    )
  );
}

我想写两个测试:

  • 检查websocket是否创建;
  • 在 websocket 连接中生成错误以实现良好的覆盖。

我写了第一个测试如下,但我不知道这里会发生什么:

it('should get Something create websocket', () => {
  service.getSomething('test').subscribe(
    data => expect(data).toBeInstanceOf(webSocket),
      fail
  ); 
});  

欢迎您的意见。谢谢。

【问题讨论】:

  • 单元测试意味着你想检查你的代码是如何工作的。但是“检查是否创建了websocket”意味着检查运行时(可能是浏览器,但在测试期间可能是NodeJS)是否支持websockets,它实际上是创建的,它连接到外部服务等——这是功能性的/集成测试,可能不是您想要在单元测试中做的事情。话虽如此,您可以通过注入一些自定义工厂来抽象 websocket 的创建,这些工厂在您的测试中被一个简单的主题覆盖,同时在应用程序运行时仍然使用WebSocketSubject
  • 我明白了,你能指出一些对我有帮助的例子吗?感谢您的评论。

标签: angular unit-testing karma-jasmine


【解决方案1】:

很难提供一个示例作为评论,所以,这里有完整的答案。

这就是我所说的:

@Injectable()
class WebSocketFactory {
  createWebSocket<T>(url: string): WebSocketSubject<T> {
    return webSocket(url);
  }
}

class SomeClass {
  constructor(private readonly factory: WebSocketFactory) {
  }

  getSomething(id: string): Observable<State | null> {
    return this.factory.createWebSocket(`ws://<websocket>`).pipe(
      catchError((err) => {
        console.error("Error in WebSocket connection: ", err);
        return concat(of(null), throwError(err));
      }),
      retryWhen((errors) => errors.pipe(delay(1000)))
    );
  }
}

如您所见,我已将 websocket 创建抽象为一个小型服务WebSocketFactory,这为我们带来了以下好处(注意:这不是一个可重复使用的代码,请阅读下文):

describe('SomeClass', () => {
  let subject = new Subject<any>();
  let service: SomeClass;

  beforeEach(() => {
    service = new SomeClass({
      createWebSocket: <T>() => subject as WebSocketSubject<T>,
    });
  });

  it('should do something useful', () => {
    subject.next(/* something */);
    service.getSomething('test').subscribe(
      data => expect(data).toBeEqual(/* something */),
        fail
    ); 
  });  
});

如您所见,现在您可以完全控制临时 websocket 的创建时间和方式,您可以访问内部,您可以发送任何值,提前关闭它等等 - 只需使用 subject 即可那不是真正的WebSocket 对象。

现在,一些注释、警告和提示。

  1. 上面的测试代码不起作用,它只是说明了一点。为了使其工作,您需要实际设置可观察测试,由于可观察对象的异步性质,这是相当重要的。这是一个很大的话题,我会从thisthis 之类的文章开始。

  2. 我正在使用一个临时模拟的东西(通过包装 subject 实现 WebSocketFactory 的对象字面量),但有些东西可以让这变得更容易和更强大:spies。但这本身就是一个大话题。

  3. 上面的例子很简单,但如果你的代码更复杂,你可能想使用 Angular 提供的 TestBed (docs) 类并做提供者覆盖而不是手动创建实例。

  4. 显然webSocket 返回WebSocketSubject (docs),所以,我已经相应地调整了代码。

  5. 除了提供完整的类作为工厂,你可以只使用一个函数(比如constructor(private readonly webSocket: typeof webSocket) {},甚至constructor(@Optional() private readonly webSocket: typeof webSocket = webSocket) {}),我通常更喜欢将东西包装在类中以便在将来的一些任务中更容易扩展呼唤它。

  6. 我知道这不是一个完整的答案,因为它没有直接解决您的“我不知道这里会发生什么”——那是因为您遇到了相当复杂的一组主题:dependency inversion——通过抽象 websocket 创建、rxjx/observable 测试(甚至一般的异步操作测试)、单元与功能/集成测试来简化。这些主题中的每一个都相当庞大,但我希望以上所有链接都能帮助您入门。

【讨论】:

  • 感谢 alx 的帮助。我会看看你提供的链接。
猜你喜欢
  • 2020-04-15
  • 2020-09-17
  • 2021-07-08
  • 2020-11-16
  • 2018-11-21
  • 2014-02-13
  • 2017-03-21
  • 2012-04-06
  • 1970-01-01
相关资源
最近更新 更多