[英]How to mock rxjs/Websocket in angular for Unit Testing
我创建了一个服务来处理 websocket 与服务器的通信,我试图为它创建单元测试,但我找不到模拟rxjs/Websocket的方法。
我在这里发现了一个类似的问题,但是我不能将它与新版本的 rxjs 一起使用。 任何帮助都会非常有帮助。
我也可以将 WebSocket 作为服务注入并在我的测试中模拟该服务,但这似乎是一种解决方法,我更喜欢更好的解决方案
这是mi代码:
socket.service.ts
//imports
@Injectable()
export class SocketService {
private baseUrl = ENV.WS_WORKSPACES_URL;
socket$: WebSocketSubject<any>;
constructor(
private userService: UserService
) { }
initSocket(): void {
this.socket$ = webSocket(`${this.baseUrl}clusters`);
const user = this.userService.getUser();
if (user) {
this.send({token: user.token});
}
}
send(data: any): void {
this.socket$.next(data);
}
}
socket.service.spec.ts
//imports
describe("SocketService", () => {
let service: SocketService;
let userServiceSpy;
let socket;
const token = "whatever";
beforeEach(() => {
userServiceSpy = jasmine.createSpyObj("UserService", ["getUser"]);
userServiceSpy.getUser.and.returnValue(null);
socket = {next: jasmine.createSpy()};
TestBed.configureTestingModule({
providers: [
{provide: UserService, useValue: userServiceSpy},
SocketService
]
});
service = TestBed.inject(SocketService);
socket = {} //this should be my mock
});
it("should be created", () => {
expect(service).toBeTruthy();
});
it("should open connection", () => {
service.initSocket();
expect(socket.next).not.toHaveBeenCalled();
expect(service.socket$).toBeDefined();
});
it("should open connection with auth", () => {
const user = {token};
userServiceSpy.getUser.and.returnValue(user);
service.initSocket();
expect(socket.next).toHaveBeenCalledWith(user);
});
it("should send a message", () => {
service.initSocket();
const message = {};
service.send(message);
expect(socket.next).toHaveBeenCalledWith(message);
});
});
这是一个特别难以解决的问题,但这里的答案实际上是让你想要模拟的东西可以注入。
这感觉像是一种解决方法,因为它似乎是不必要的工作。 大多数特定于 Angular 的库会导出一个服务,该服务就像一个工厂来解耦它,这太棒了。 但是 rxjs不是Angular 库,尽管框架非常依赖它。
TLDR; 您将不得不将webSocket
包裹在可注射的东西中。
由于模块的工作方式, webSocket
构造函数通常不可模拟。 根据您使用的模块类型以及您是否可以控制导出模块,您可以进行一些调整,但它们只是解决方法。 演变一直朝着导入只读的方向发展,这逐渐打破了大多数现有的解决方法。 jasmine 的 github 上的这个问题涵盖了解决方法以及(最终)通用解决方案难以捉摸的原因。
Jasmine在其常见问题解答中提供了一些官方指导:
- 对要模拟的东西使用依赖注入,并从规范中注入间谍或模拟 object。 这种方法通常会导致规范和被测代码的可维护性改进。 需要模拟模块通常是紧密耦合代码的标志,修复耦合而不是使用测试工具解决它可能是明智的。
总是很好的建议,这当然是一种依赖。 并且我们要模拟的功能与构造函数非常耦合:旁注,这里很难指责 rxjs 团队。 令人沮丧。 这是一个需要特定于框架的解决方案的问题。
这里有两个选择,都不错。
这很容易,而且令人尴尬的是,这不是我尝试的第一件事。 只需创建一个新服务并为其提供一个具有相同签名的公共方法:
@Injectable()
export class WebSocketFactoryService {
constructor(){}
public makeSocket<T>(urlConfigOrSource: string | WebSocketSubjectConfig<T>): WebSocketSubject<T> {
return webSocket<T>(urlConfigOrSource);
}
}
这个稍微麻烦一些,但您不必为工厂服务创建新文件。 带上不止一种工具也很好。
这是一个测试套件的Stackblitz 链接,该应用程序的服务需要创建 websocket 和注入所述服务的组件(它也没有“缺少核心 js”问题。) 这个确切场景的指南,虽然有点难找。
首先你制作一个InjectionToken
,因为这不是 class:
// I only imported as rxjsWebsocket because I wanted to use webSocket in my service
import { webSocket as rxjsWebsocket, WebSocketSubject } from 'rxjs/webSocket';
// Fun fact: You can use "typeof rxjsWebsocket" as the type to cleanly say "whatever that thing is"
export const WEBSOCKET_CTOR = new InjectionToken<typeof rxjsWebsocket>(
'rxjs/webSocket.webSocket', // This is what you'll see in the error when it's missing
{
providedIn: 'root',
factory: () => rxjsWebsocket, // This is how it will create the thing needed unless you offer your own provider, which we'll do in the spec
}
);
然后你需要告诉你的服务像任何其他依赖一样注入它:
@Injectable({
providedIn: 'root',
})
export class SocketService {
socket$: WebSocketSubject<any>;
constructor(
@Inject(WEBSOCKET_CTOR) private _webSocket: typeof rxjsWebsocket
) {
this.socket$ = this._webSocket<any>('https://stackoverflow.com/');
}
messages(): Observable<any> {
return this.socket$.asObservable();
}
send(data: any): void {
this.socket$.next(data);
}
}
问题解决了!
哦,等等,测试。 首先,您需要实际制作一个模拟。 有几种方法,但我将其用于我的注入令牌版本:
// Mocking the websocket
let fakeSocket: Subject<any>; // Exposed so we can spy on it and simulate server messages
const fakeSocketCtor = jasmine
.createSpy('WEBSOCKET_CTOR')
.and.callFake(() => fakeSocket); // need to call fake so we can keep re-assigning to fakeSocket
如果您改为提供服务,您可能需要一个间谍 object
const fakeSocketFactory = jasmine.createSpyObj(WebSocketFactoryService, 'makeSocket');
fakeSocketFactory.makeSocket.and.callFake(() => fakeSocket);
你仍然想要一个可以不断重置的暴露主题。
创建服务很简单,只需使用构造函数!
beforeEach(() => {
// Make a new socket so we don't get lingering values leaking across tests
fakeSocket = new Subject<any>();
// Spy on it so we don't have to subscribe to verify it was called
spyOn(fakeSocket, 'next').and.callThrough();
// Reset your spies
fakeSocketCtor.calls.reset();
// Make the service using the ctor
service = new SocketService(fakeSocketCtor);
// or service = new SocketService(fakeSocketFactory); if you did that one
});
现在你可以像几个小时前想做的那样自由地测试 Observables!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.