sbatson5 / firestore-jest-mock

Jest Helper library for mocking Cloud Firestore
https://www.npmjs.com/package/firestore-jest-mock
178 stars 58 forks source link

How to add current Logged in User #64

Closed AlphaNiner1415 closed 3 years ago

AlphaNiner1415 commented 3 years ago

I want to know how to add a current logged in user for the mock firebase. I have a function in a different file from my test file with the function:

export const getUserDocument = async (uid) => {
    if (!uid) return null;
    try {
        const userDocument = await firestore.doc(`users/${uid}`).get();
        return {
            uid,
            ...userDocument.data(),
        };
    } catch (error) {
        console.error("Error fetching user", error);
    }
};

Then in a separate test file I put:

it("should return a user if the function is called with a uid provided", async () => {
        //Promises need to be returned to properly throw errors
        return getUserDocument("a1").then((uid, data) => {
            //expect(mockCollection).toHaveBeenCalledWith('users');
            expect(mockDoc).toHaveBeenCalledWith("users/a1");
            expect(uid).toEqual("a1");
            expect(data).not.toBeNull();
        });
    });

Expected behavior: the function returns the uid of "a1" and some data Actual Behavior:

console.error
    Error fetching user Error [FirebaseError]: Missing or insufficient permissions.
        at new FirestoreError (/Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/util/error.ts:217:5)
        at fromRpcStatus (/Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/remote/serializer.ts:154:10)
        at fromWatchChange (/Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/remote/serializer.ts:476:33)
        at PersistentListenStream.Object.<anonymous>.PersistentListenStream.onMessage (/Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/remote/persistent_stream.ts:581:25)
        at /Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/remote/persistent_stream.ts:461:21
        at /Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/remote/persistent_stream.ts:514:18
        at /Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/util/async_queue_impl.ts:168:14
        at processTicksAndRejections (internal/process/task_queues.js:93:5) {
      code: 'permission-denied',
      name: 'FirebaseError',
      toString: [Function]
    }

      86 |         };
      87 |     } catch (error) {
    > 88 |         console.error("Error fetching user", error);
         |                 ^
      89 |     }
      90 | };

It would seem I need to authenticate first which doesn't make sense because the mock library isn't suppose to have any installed rules, unless it's not using the mock library

sbatson5 commented 3 years ago

Thanks for posting. Based off the path /node_modules/@firebase/firestore/src/util/error.ts:217:5 it looks like your test is hitting the real firestore, so it isn't using the mock. We also don't throw any errors like that, so my guess it comes from firebase directly.

How do you set up the mock in your test suite? Sometimes jest can be frustrating with the way it hoists functions calls, so the mock might be getting called after your app spins up.

AlphaNiner1415 commented 3 years ago

So this is how my testing file is setup:

At the top of the file I have:

import { updateUser, currentUserListener, returnFirestore, createNewsArticle, getUserDocument } from './firebase';
import "firebase/auth";

const { mockFirebase } = require('firestore-jest-mock');
const { mockInitializeApp } = require('firestore-jest-mock/mocks/firebase');

mockFirebase({
        database: {
            users: [
                {
                    id: "a1",
                    displayName: "Homer Simpson",
                    phoneNumber: "1234815785",
                    email: "example@hotmail.com",
                    dateOfBirth: new Date("10 February, 2000"),
                    photoURL: "http://google.co.th",
                },
                {
                    id: "a2",
                    displayName: "Bart Simpson",
                    phoneNumber: "3874384783",
                    email: "example@gmail.com",
                    dateOfBirth: new Date("10 February, 2000"),
                    photoURL: "http://google.co.th",
                },
            ],
            news: [
                {
                    id: "1",
                    authorName: "Homer Simpson",
                    datePublished: "2000/01/01",
                    dateUpdated: new Date(),
                    category: 'Sports',
                    hits: 2,
                    content: "jskl;dfja;lksdjfa",
                    previewContent: "kdf;alsdkjfa;lskdf",
                    title: "News Article 1",
                }
            ]
        }
    });

And this is the start of the test:

describe('The getUserDocument() function', () => {

    let firebase;
    let firestore;
    let autho;

    beforeEach(() => {
        jest.clearAllMocks();
        firebase = require("firebase"); // or import firebase from 'firebase';
        autho = firebase.auth();
        firestore = firebase.firestore();

    });

Now I'm starting to think it's all about variable naming, because now inside the files that stores the function (firebase.js) I have the variables initialized like this: ( the firebaseConfig is of course initialized with real app settings, I'm just replacing it with placeholders so as to not expose my app )

const firebaseConfig =  {
    apiKey: "MY APP'S KEY",
    authDomain: "PROJECTID.firebaseapp.com",
    projectId: "PROJECTID",
    storageBucket: "PROJECTID.appspot.com",
    messagingSenderId: "RANDOMNUMBER",
    appId: "APPID",
    measurementId: "MEASUREMENTID",
};
const fire = firebase.initializeApp(firebaseConfig);
export const auth = firebase.auth();
export const firestore = firebase.firestore();

And so inside the function inside firebase.js when I call the doc I call it like so: firestore.doc(users/${uid}).get(); I'm thinking if I change it to: firebase.firestore().doc(users/${uid}).get();

It might allow the mockFirebase to override it. However, I would prefer a way where I didn't have to do that for each and every function.

AlphaNiner1415 commented 3 years ago

Ok so update, changing firestore.doc(users${uid}).get(); to firebase.firestore().doc(users/${uid}).get(); doesn't work

However, if I copy the whole function from the file with my firebase functions and paste it into my test file below the describe then that successfully tests it:

describe('The getUserDocument() function', () => {
    const getUserDocument = async (uid) => {
    if (!uid) return null;
    try {
        const userDocument = await firestore.doc(`users/${uid}`).get();
        return {
            uid,
            ...userDocument.data(),
        };
    } catch (error) {
        console.error("Error fetching user", error);
    }
   };
    let firebase;
    let firestore;
    let autho;

    beforeEach(() => {
        jest.clearAllMocks();
        firebase = require("firebase"); // or import firebase from 'firebase';
        autho = firebase.auth();
        firestore = firebase.firestore();

    });

This works

sbatson5 commented 3 years ago

If you move the mockFirebase call into the describe block, does that fix it? Having it live outside of the test might be causing a scoping issue. Take a look at some of our tests as an example: https://github.com/Upstatement/firestore-jest-mock/blob/master/__tests__/query.test.js#L12

sbatson5 commented 3 years ago

Closing due to inactivity. Feel free to re-open if you still have questions