Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@getcircuit/firestore-test

Package Overview
Dependencies
Maintainers
19
Versions
186
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@getcircuit/firestore-test

<!-- @import "[TOC]" {cmd="toc" depthFrom=2 depthTo=6 orderedList=false} -->

  • 14.23.0
  • unpublished
  • npm
  • Socket score

Version published
Weekly downloads
0
Maintainers
19
Weekly downloads
 
Created
Source

@getcircuit/firestore-test

This package is comprised of a set of utilities to help you write tests for your Firestore code. It mainly provides two significant features:

  • A set of utilities to help you seed Circuit's firestore data inside a jest test environment.
  • A tiny CLI to help you get the emulator going with the appropriate configuration for easy testing.

Usage

CLI

The CLI is a simple wrapper around the firebase CLI, but with the project set to test and pointing to this package's firebase.json configuration file.

# Start the emulator indefinitely
firestore-test emulator:start

# Start the emulator and run a command
firestore-test emulator:exec "<command>"

Example:

# Start the emulator and run jest
firestore-test emulator:exec "jest"

Utilities

Testing utilities
export {
  testFirestore,
  getUniqueTestRootRef,
  waitForSnapshotListenersInSync,
  waitLoadedAccessor,
  spySignal,
} from '@getcircuit/firestore-test'
  • testFirestore is a function that returns a Firestore instance that is connected to the emulator.

  • getUniqueTestRootRef is a function that returns a DocumentReference that is unique to the current test. This is useful when you want to seed data that is specific to the current test without having to seed a whole team.

import { getUniqueTestRootRef } from '@getcircuit/firestore-test'

const ref = getUniqueTestRootRef() // DocumentReference to /test-suite/{randomId}
  • waitForSnapshotListenersInSync() is a function that returns a promise that resolves when all active snapshot listeners are in sync with the remote firestore. This is useful when you want to guarantee that a test is waiting for a firestore document to be updated.

  • waitLoadedAccessor(signal) is a function that returns a promise that resolves when the given LoadableAccessor is loaded. This is useful when you want to guarantee that a test is waiting for an LoadableAccessor to be loaded.

  • spySignal(signal) is a function that returns a jest.Mock that is spying on the given LoadableAccessor. This is useful when you want to verify that a signal has been called after any of its dependencies were updated.

Seed utilities
export {
  seedDepots,
  seedMembers,
  seedPatches,
  seedPlan,
  seedPlanDrivers,
  seedRoute,
  seedStops,
  seedTeam,
  seedUsers,
} from '@getcircuit/firestore-test'

Each seeding utility accepts a DeepPartial version of the data being seeded. Properties that are not explicitly set will fallback to what has been defined inside the src/firebase/data-factories directory.

Each seeded model may or may not receive an id property. If no id is provided, a random one will be generated.

await seedStops({
  collection,
  stops: [
    { id: 'stop-1', name: 'Stop 1' }, // Stop 1 will have a known id
    { name: 'Stop 2' }, // Stop 2 will have a random id
  ],
})

While there are individual methods to seed users, members, depots, etc, you don't need to use them directly. For example, to seed a team with members and their users, you can use _only_ the seedTeam method:

import { seedTeam } from '@getcircuit/firestore-test'

const teamSelectors = await seedTeam({
  members: [
    { id: 'john', name: 'John Doe' },
    { id: 'jane', name: 'Jane Doe' },
  ],
})

This will automatically create the users for the specified members, and link their DocumentReferences accordingly.

We can then select these references with the help of the teamSelectors object:

teamSelectors.membersRef() // => CollectionReference<Member>
teamSelectors.memberRef('john') // => DocumentReference<Member>
teamSelectors.userRef('john') // => DocumentReference<User>
About seeding users via members

Since each test is expected to seed its own data, the user collection is shared between all tests. Creating an user via their member is done by using the same id given to the member. So if two different tests created a member with id john, both tests would try to write data to the same firestore document.

To prevent this, the seedTeam function prefixes the user id with the id of the team.

const teamSelectors = await seedTeam({
  id: 'team-1',
  members: [
    { id: 'john', name: 'John Doe' },
    { id: 'jane', name: 'Jane Doe' },
  ],
})

teamSelectors.userRef('john').id // => 'team-1-john'

In case one wants to verify the id of a user, the teamSelectors object provides a userId method that will return the prefixed id:

teamSelectors.userId('john') // => 'team-1-john'

This is one of the reasons the returned selectors are particularly useful, as this prefixing logic is encapsulated inside them.

About seeding documents with cross-references

As we saw, some of the seeding methods allow you to seed various firestore models at once. While this can speed up writing tests, it can also lead to some confusion when it comes to cross-references.

For example, imagine that we want to seed a team with one member that is assigned to a depot. At the moment of calling seedTeam, nothing exists, so there's no way to link the member-to-be to the depot-to-be.

const teamSelectors = await seedTeam({
  depots: [{ id: 'depot-1' }],
  members: [
    {
      id: 'john',
      name: 'John Doe',
      depot: ???, // How to link the member to the depot?
    },
  ],
})

There are two ways to solve this:

  • Use the deferred argument of the seeding methods

This is the most concise way, as it allows you to, instead of providing the seeding arguments directly, pass a function that will be called with the selectors. This function should return the data that will be used to seed the model.

const teamSelectors = await seedTeam((teamSelectors) => ({
  depots: [{ id: 'depot-1' }],
  members: [
    {
      id: 'john',
      name: 'John Doe',
      depot: teamSelectors.depotRef('depot-1'),
    },
  ],
}))
  • Use the seeding methods separately

This is the most verbose way, but it allows you to have full control over the references. However, you will need to manually link the references and deal with the "user id" conflict mentioned on the About seeding users via members section.

const teamSelectors = await seedTeam({
  depots: [{ id: 'depot-1' }],
})

const userSelectors = await seedUsers({
  teamRef: teamSelectors.teamRef,
  users: [
    {
      id: 'john',
      name: 'John Doe',
    },
  ],
  mapId: (id) => teamSelectors.userId(id),
})

const memberSelectors = await seedMembers({
  teamRef: teamSelectors.teamRef,
  members: [
    {
      id: 'john',
      name: 'John Doe',
      depot: teamSelectors.depotRef('depot-1'),
    },
  ],
})

Writing a test

Each test that connects to the firestore emulator is expected to be self-contained. This means that each test should seed its own data. Cleaning up is not necessary.

import { seedTeam, seedRoute } from '@getcircuit/firestore-test'

test('route is created', async () => {
  // Seeds a team with one member
  const { teamRef, memberRef, userRef } = await seedTeam({
    members: [{ id: 'chris' }],
  })

  // Seeds a route with linking it to the driver user and member documents
  const { routeRef } = await seedRoute(teamRef, {
    route: {
      driver: userRef('chris'),
      member: memberRef('chris'),
    },
  })

  expect('...').toEqual('...')
})
Configuring the test environment

@getcircuit/firestore-test provides a JestConfig file to serve as a base for your project's jest setup.

It can be imported from the @getcircuit/firestore-test/jest-config entry-point.

// myProject/jest.config.js
const shared = require('@getcircuit/firestore-test/jest-config')

module.exports = {
  ...shared,
  // your custom config
}
Conflict-free firestore root

In case your Firestore-related test doesn't need a whole team, you can use the getUniqueTestRootRef method to get a unique document ref pointing to test-suite/{randomId}. This is useful to avoid having to create a unique collection in each test to prevent conflicts.

import { getUniqueTestRootRef } from '@getcircuit/firestore-test'

test('test 1', async () => {
  const rootRef = getUniqueTestRootRef()
  const patches = rootRef.collection('patches')

  // do stuff with this patches collection
})

test('test 2', async () => {
  const rootRef = getUniqueTestRootRef() // this is different from the previous test
  const patches = rootRef.collection('patches') // the collections in both tests have the same name, but live in different paths

  // do stuff with this patches collection
})

FAQs

Package last updated on 27 Jul 2023

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc