firebase-mock icon indicating copy to clipboard operation
firebase-mock copied to clipboard

Full setup example Firestore + React + Jest

Open artooras opened this issue 7 years ago • 11 comments

Hi. While trying to set up firebase-mock in my project, I fail to 'connect the dots'. I have the following setup.

firebaseSetup.js

import firebase from 'firebase/app'
import 'firebase/auth'
import 'firebase/firestore'


if (!firebase.apps.length) {

  var config = {
    apiKey: "...",
    authDomain: "...",
    databaseURL: "...",
    projectId: "...",
    storageBucket: "...",
    messagingSenderId: "..."
  }
  
  firebase.initializeApp(config)
}

const firebaseAuth = firebase.auth()
const firestore = firebase.firestore()
firestore.settings({timestampsInSnapshots: true})

export default firebase
export {
  firebaseAuth,
  firestore
}

AppBody.js

import React from 'react'
import PropTypes from 'prop-types'
import firebase, {firestore} from './firebaseSetup'

const propTypes = {
  userID: PropTypes.string
}

class AppBody extends React.Component {
  state = {
    data: undefined
  }
  componentWillMount = () => {
    const userRef = firestore.collection('users').doc(this.props.userID)
    this.dbData = userRef.collection('data')
    this.dbDataUnsubscribe = this.dbData.onSnapshot(data => this.setState({data}))
  }
  componentWillUnmount = () => {
    this.dbDataUnsubscribe()
  }
  render() {...}
}

Now, the tricky part - AppBody.test.js

import React from 'react'
import proxyquire from 'proxyquire'
import {render} from 'react-testing-library'

const AppBody = proxyquire('./AppBody.js', {
  firebase: mocksdk
})

jest.mock('./firebaseSetup.js', () => {
  const firebasemock = require('firebase-mock')
  const mockauth = new firebasemock.MockAuthentication()
  const mockfirestore = new firebasemock.MockFirestore()
  const mocksdk = new firebasemock.MockFirebaseSdk(null, () => mockauth, () => mockfirestore)
  const firebase = mocksdk.initializeApp()
  const firestore = firebase.firestore()
  const firebaseAuth = firebase.auth()
  return firebase, {firebaseAuth, firestore}
})

describe('AppBody', () => {
  it('renders', () => {
    const {getByText} = render(
      <AppBody 
        userID={'aaa'} 
      />
    )
    expect(getByText('Today')).toBeInTheDocument()
  })
})

Now, obviously, my test file is incorrect. For one, mocksdk is undefined in line 6. So, my question is, how do I setup firebase-mock correctly so that my AppBody component at least renders and retrieves initial data?

Thank you very much in advance!

artooras avatar Oct 25 '18 12:10 artooras

I struggle to set it up as well. :thinking: A better example would be very appreciated!

balazsorban44 avatar Oct 29 '18 18:10 balazsorban44

Because you use mocksdk before declare, a mocksdk was declare at line 13. I suggest you to see tutorial at Tutorial: Integrating with jest and you don't necessary to use proxyquire when you use jest.

Eji4h avatar Oct 29 '18 20:10 Eji4h

Thanks @Eji4h. I have removed proxyquire, and my updated AppBody.test.js looks like this:

import React from 'react'
import {render} from 'react-testing-library'

jest.mock('./firebaseSetup.js', () => {
  const firebasemock = require('firebase-mock')
  const mockauth = new firebasemock.MockAuthentication()
  const mockfirestore = new firebasemock.MockFirestore()
  const mocksdk = new firebasemock.MockFirebaseSdk(
    null, // RTDB
    () => mockauth, 
    () => mockfirestore
  )
  const firebase = mocksdk.initializeApp()
  const firestore = firebase.firestore()
  const firebaseAuth = firebase.auth()
  return firebase, {firebaseAuth, firestore}
})

describe('AppBody', () => {
  it('renders', () => {
    const {getByText} = render(
      <AppBody 
        userID={'aaa'} 
      />
    )
    expect(getByText('Today')).toBeInTheDocument()
  })
})

The return statement of my mock matches that of firebaseSetup.js, as per instructions. However, when I run my test, I get an error saying:

TypeError: this.dbData.onSnapshot is not a function

referring to this line in AppBody.js:

this.dbDataUnsubscribe = this.dbData.onSnapshot(data => this.setState({data}))

So, what am I doing wrong?

artooras avatar Oct 30 '18 10:10 artooras

The only library that needs mocking is the 'firebase/app' one, so your mock should apply to that instead and simply return the mock SDK -- the 'instructions' (if one can call them that) are misleading:

import React from 'react'
import {render} from 'react-testing-library'

// Import firebase from your firebase init script:
import firebase from './firebaseSetup'

// Now mock 'firebase/app`:
jest.mock('firebase/app', () => {
  const firebasemock = require('firebase-mock')
  const mockauth = new firebasemock.MockAuthentication()
  const mockfirestore = new firebasemock.MockFirestore()
  return new firebasemock.MockFirebaseSdk(
    null, // RTDB
    () => mockauth, 
    () => mockfirestore
  )
})

describe('AppBody', () => {
  beforeAll(() => {
    // Add some data to your mock firebase if you need to...
    firebase.firestore().autoFlush()
    firebase.firestore().collection('collectionId').doc('docId').set({foo: 'bar'})
  })

  it('renders', () => {
    const {getByText} = render(
      <AppBody 
        userID={'aaa'} 
      />
    )
    expect(getByText('Today')).toBeInTheDocument()
  })
})

froddd avatar Jan 21 '19 13:01 froddd

Thanks, I think I'll give it another go and try your suggestion. I have since implemented a mock of my data-handling file, but maintenance is a bit of a pain in the bum... It would be much easier if I could simply mock the firebase bit.

Is there a way to set initial data for the tests? I couldn't really find that in the docs either.

artooras avatar Jan 21 '19 15:01 artooras

AFAIK the only way to set initial data is to manually set each document, so:

const collectionRef = firebase.firestore().collection('collectionId');
ref.doc('docId').set({foo: 'bar'});
ref.doc('anotherDocId').set({foo: 'baz'});
// etc...

If you're only testing for one user and each user has its own document, I guess you'll only have one call to set().

froddd avatar Jan 21 '19 15:01 froddd

@froddd : I tried your suggestion above, mocking the 'firebase/app' rather than my '@/firebase-setup', but it doesn't work for me; the mocking code runs but it doesn't seem to affect the actual firebase calls my source code makes -- they're still using the un-mocked firebase. For instance, in my test code firebase.firestore().autoFlush() is not a function.

Any further hints? It would be great to see a fully worked example of firebase-mock with jest.

garyo avatar Feb 22 '19 16:02 garyo

And if I try it a different way, I get firebase.apps.length: Cannot read property 'length' of undefined. This seems to be because firebase is now the mock object:

{ database: { [Function: MockFirebaseDatabase] ServerValue: { TIMESTAMP: [Object] } },
  auth:
       { [Function: MockFirebaseAuth]
         EmailAuthProvider: { [Function: EmailAuthProvider] PROVIDER_ID: 'password', credential: [Function] },
         GoogleAuthProvider: { [Function: GoogleAuthProvider] PROVIDER_ID: 'google.com', credential: [Function] },
         TwitterAuthProvider: { [Function: TwitterAuthProvider] PROVIDER_ID: 'twitter.com', credential: [Function] },
         FacebookAuthProvider: { [Function: FacebookAuthProvider] PROVIDER_ID: 'facebook.com', credential: [Function] },
         GithubAuthProvider: { [Function: GithubAuthProvider] PROVIDER_ID: 'github.com', credential: [Function] } },
  firestore:
       { [Function: MockFirebaseFirestore]
         FieldValue: { [Function: MockFirestoreFieldValue] delete: [Function], serverTimestamp: [Function] } },
  storage: [Function: MockFirebaseStorage],
  messaging: [Function: MockFirebaseMessaging],
  initializeApp: [Function: initializeApp] }

which as you can see doesn't have an apps member. I can work around that by checking for apps in my init code, but then I'm back to this error:

TypeError: _myFirebase.db.collection(...).onSnapshot is not a function

so maybe firebase-mock just doesn't implement what I need?

garyo avatar Feb 22 '19 17:02 garyo

Aha, I see #81 is about missing onSnapshot(). That's core to how I use Firestore so I guess this won't work for me.

garyo avatar Feb 22 '19 17:02 garyo

I think firebase has moved on a bit since the last commits to this mocking library -- I've just come across some more missing functionality, which I may create a PR for.

froddd avatar Feb 24 '19 19:02 froddd

Just wanted to mention that the missing onSnapshot() functionality is added in pull-request #130, but just hasn't been accepted yet.

In the mean-time, you can add that functionality by running npm install BrianChapman/firebase-mock#issue-81-onsnapshot.

It's worked well so far in my project.


P.S. By the way, I agree with the others that the docs really need some improvement. The tutorials are... not really tutorials because they leave out important contextual information that let you produce actual working tests.

Best would be if there were a self-contained demo tutorial that is actually runnable.

In the meantime, the link to the setup tutorial page should be made more prevalent on the readme/homepage, and it should say: "Use the mocksdk variable the same way you would use the var firebase = require("firebase/app"); variable"

Venryx avatar Oct 23 '19 10:10 Venryx