[英]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.