Maciej
Maciej

Reputation: 59

How to intercept requests to Firebase API using msw - mock service worker? React app testing

I encountered problems while testing my demo React app.

I use msw to intercept Firebase API requests and I continuously keep getting error in my terminal.

Please note, that I have Firebase Authentication implemented so only authorized users in my app can read.

https://travelsi-default-rtdb.firebaseio.com/posts.json?auth=USER-ACCESS-TOKEN

However does user access token (attached to url) really matter if I use msw to mock/intercept fetch requests and fake the response? Or what else causes the error when running test?

console.error
      GET https://travelsi-default-rtdb.firebaseio.com/posts.json?auth= net::ERR_FAILED

      at FetchInterceptor.<anonymous> (node_modules/@mswjs/interceptors/src/interceptors/fetch/index.ts:91:17)
      at step (node_modules/@mswjs/interceptors/lib/interceptors/fetch/index.js:59:23)
      at Object.next (node_modules/@mswjs/interceptors/lib/interceptors/fetch/index.js:40:53)
      at fulfilled (node_modules/@mswjs/interceptors/lib/interceptors/fetch/index.js:31:58)

For testing I use React Testing Library & Jest. I use msw (Mock Service Worker) for intercepting API calls. The app itself uses Firebase Real Time Database & Firebase Auth, Redux Toolkit for state management.


I am testing AllPosts.js component where I dispatch action creator thunk dispatch(fetchPosts(accessToken) that sends a GET request to Firebase API.

AllPosts.js

import classes from './AllPosts.module.css';
import SinglePost from './SinglePost';
import { fetchPosts } from '../../../../store/slices/action-creators/fetchPosts';
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';

const AllPosts = () => {
  const dispatch = useDispatch();
  const posts = useSelector(state => state.posts.postsToRender);
  const { accessToken } = useSelector(state => state.auth.userData);

  useEffect(() => {
    dispatch(fetchPosts(accessToken));
  }, []);

  return (
    <div className={classes['all-posts']}>
      <div className={classes['all-posts__wrapper']}>
        {posts.map(post => {
          return (
            <SinglePost
              city={post.city}
              country={post.country}
              date={post.date}
              description={post.description}
              id={post.id}
              img={post.img}
              tag={post.tag}
              title={post.title}
              feelings={post.feelings}
              key={post.id}
            />
          );
        })}
      </div>
    </div>
  );
};

export default AllPosts;

fetchPosts.js

import { postsActions } from '../posts-slice';
import { FIREBASE_API } from '../../../config/firebase';
import { uiActions } from '../ui-slice';

export const fetchPosts = accessToken => {
  return async dispatch => {
    const fetchData = async () => {
      const res = await fetch(`${FIREBASE_API}/posts.json?auth=${accessToken}`);

      if (!res.ok) throw new Error('Problems with getting data. Please try again 🤔.');

      const data = await res.json();

      const posts = [];

      for (const key in data) {
        posts.push(data[key]);
      }
      return posts;
    };

    try {
      dispatch(
        uiActions.setNotification({
          status: 'loading',
          type: 'get',
          message: '🦢 loading posts ...',
        })
      );

      const posts = await fetchData();
      dispatch(postsActions.getPosts({ posts: posts }));
      dispatch(
        uiActions.setNotification({
          status: 'success',
          type: 'get',
          message: 'completed',
        })
      );
    } catch (err) {
      dispatch(
        uiActions.setNotification({
          status: 'error',
          type: 'get',
          message: err.message,
        })
      );
    }
  };
};

AllPosts.test.js

import { screen, render } from '@testing-library/react';
import AllPosts from './AllPosts';
import { Provider } from 'react-redux';
import { createStore } from '../../../../store/index';

describe('AllPosts component', () => {
  test('posts are rendered correctly', async () => {
    render(
      <Provider store={createStore()}>
        <AllPosts />
      </Provider>
    );
    await wait();
    screen.debug();
  });
});

const wait = () => new Promise(resolve => setTimeout(resolve, 2000)); //DUMMY FUNCTION USED JUST FOR MY PURPOSES

MY MSW SETUP Nothing fancy here. I actually just followed msw docs.

handlers.js

import { rest } from 'msw';

export const handlers = [
  rest.get('https://travelsi-default-rtdb.firebaseio.com/posts.json', (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json({
        p1: {
          city: 'city 1',
          country: 'country',
          date: '01.01.2020',
          description: 'description',
          feelings: {
            f1: {
              reaction: 1,
            },
          },
          id: p2,
          img: 'img',
          tag: '#tag',
          title: 'title 2',
        },
        p1: {
          city: 'city 2',
          country: 'country',
          date: '01.01.2020',
          description: 'description',
          feelings: {
            f1: {
              reaction: 1,
            },
          },
          id: p2,
          img: 'img',
          tag: '#tag',
          title: 'title 2',
        },
      })
    );
  }),
];

server.js

import { setupServer } from 'msw/node';
import { handlers } from './handlers';

export const server = setupServer(...handlers);

setupTests.js

import '@testing-library/jest-dom';
import { server } from './mocks/server';

beforeAll(() => server.listen());

afterEach(() => server.resetHandlers());

afterAll(() => server.close());

What causes the error? How can I successfully intercept Firebase API calls with msw, get fake response and render items in test?

Thank you in advance. If you need any other implementation details, please let me know ;)

Upvotes: 1

Views: 1360

Answers (1)

krock
krock

Reputation: 513

Just ran into a similar issue. What the problem was for me, and what I think it is here, is that the mocked URL is https://travelsi-default-rtdb.firebaseio.com/posts.json without any query string parameters. However, your API call has a "?auth=" at the end of it.

But the msw documentation reads:

Provided an exact request URL string, only those request that strictly match that string are mocked.

https://mswjs.io/docs/basics/request-matching

So per the documentation, you'd want to do one of these two options:

// Matches:
// - /users/admin
// - /users/octocat
rest.get('/users/*', responseResolver),

or:

import { setupWorker, rest } from 'msw'

setupWorker(
  // Given "POST https://api.backend.dev/user/abc-123" request,
  rest.post('https://api.backend.dev/user/:userId', (req, res, ctx) => {
    // `userId` value becomes "abc-123"
    const { userId } = req.params
  }),
)

Hope this helps any future fellow googlers and/or ChatGPT users!

Upvotes: 0

Related Questions