How to unit test switchMap and other operations with Jest Angular

137 views Asked by At

Angular 15, want to test switchMap, filter, tap and take.

I'm trying to test my observable:

private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null)
private refreshTokenSubject$ = this.refreshTokenSubject.asObservable()

handleRefresh(){
 return this.refreshTokenSubject$.pipe(
   tap(() => console.log('refresh token')),
   filter((token) => token)),
   take(1),
   switchMap((token) => {
      return next.handle(this.addToken(req,token);
   })
 ) 
}

I'm doing this:

beforeEach(()=>{
  interceptor = TestBed.inject(RefreshTokenInterceptor)
  requestMock = new HttpRequest("GET", "/fakeRequest")
})

it("should", () => {
  interceptor["addToken"] = jest.fn().mockReturnValue(requestMock)
  
  const next = {
    handle: jest.fn().mockImplementation(()=>{
      return of()
    }
  } as HttpHandler

  const result = interceptor["refreshTokenSubject$"]
  interceptor["handleRefresh"](requestMock, next)
  result.subscribe(() => {
    expect(next.handle).toBeCalled()
  })
})

the test pass, but coverage is saying that the tap, filter, take and switchMap lines are not being tested

2

There are 2 answers

0
Vivick On

Coverage is not necessarily a good metric for test quality (sometimes things can't be covered). That being said, you can still work you way through 100% coverage (as reported by static analysis and coverage reports), and then real 100% coverage (every single possible path is covered).

  • tap => mock console.log and assert on the amount of invocations
  • filter => test at least once for a truthy token and once for a falsy token
  • take => mock switchMap to assert on the amount of times your callback is invoked
  • switchMap => always the tricky one to test. Add a controlled delay/debounce in your test subject's emissions so that you can emit before it expires and the switchMap is guaranteed to occur. Then assert on the amount of invocations. If you can mock your handle to check that it has been "cancelled"/"switched" correctly and semantically
0
MoxxiManagarm On

There are several issues in your code:

Missing ;

Missing ; can actually lead to unexpected side effects in some cases. Please try to always set them.

You do not test asynchronously

The testcase ends before the subscribe block, therefor the expect, is called. There are several ways to test asynchronous code. The easiest way at this point is to add the done handler.

it("should", done => {
  interceptor["addToken"] = jest.fn().mockReturnValue(requestMock);
  
  const next = {
    handle: jest.fn().mockImplementation(()=>{
      return of()
    }
  } as HttpHandler;

  const result = interceptor["refreshTokenSubject$"];
  interceptor["handleRefresh"](requestMock, next);
  result.subscribe(() => {
    expect(next.handle).toBeCalled();
    done();
  })
})

Even tho I recommend using jest marbles over this.

Your refresh token is null

How do you expect the stream to pass the filter function? Your refresh token always starts with null and therefor won't reach the subscription handler.