-
Notifications
You must be signed in to change notification settings - Fork 58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Assert access on a Firestore document path #52
Comments
We've been using this matcher in our own production code for some time, and have found it extremely handy. I don't know how to export Jest matchers from a package, but it must be possible with minimal work on users' end, considering jest-extended is a thing. |
Sorry it's taken me so long to read through this but I like the concept a lot. And it doesn't make our API more complicated. I'm not finding an easy way for us to have this automatically added to jest, but it looks like we can follow what they do on this library: The end-user can just call |
Absolutely! It would be simple to write up a function to export. Perhaps we would even export a function that would build |
I'm working on a branch that will make the |
Update on that branch. It's very nearly ready. For the sake of my own dependent projects, I'm waiting on firestore-jest-mock's TypeScript definitions before I get that work merged in. |
Looking at that today! |
Summary
I propose we add some way for users to assert access to specific document or collection paths.
Basic example
We should be able to assert transaction or batched writes like this:
Here we assert that the Firestore API was called with the expected parameters while asserting that one of those parameters defined a specific Firestore document path.
Standalone reads and writes are a bit trickier, since calling
ref.update({ ... })
doesn't presently informmockUpdate
about the path on which the access was made. I don't have any good ideas for that yet, but at the very least we should have some matchers for our mockDocumentReference
orCollectionReference
types.Motivation
Since we now have subcollection support as of #35, users now have a problem when trying to assert access to a specific document path. I do not mean access permissions, those are handled by Firestore rules and beyond the scope of firestore-jest-mock. Presently, the normal way to assert correct document access is by using the variations of the following logical argument:
mockDoc
was called aftermockCollection
, using eitherexpect(...).toHaveBeenCalledAfter
orexpect(...).not.toHaveBeenCalledBefore
, preferably both.mockCollection
was called with the correct collection IDc
, usingexpect(...).toHaveBeenCalledWith
, or more preferablyexpect(...).toHaveBeenNthCalledWith
.mockDoc
was called with the correct document IDd
, usingexpect(...).toHaveBeenCalledWith
, or more preferablyexpect(...).toHaveBeenNthCalledWith
.c/d
.This works well enough when the unit under test only deals with one Firestore document, and calls these methods exactly once each. But that is often not the case. Nested subcollections break the assumption that
mockCollection
was called only beforemockDoc
, and therefore our conclusion no longer holds true in every case. We now need to factor in which call tomockDoc
ormockCollection
we're asserting against, and then any reordering of accesses breaks unit tests, even when such access has only to do with the construction ofDocumentReference
s.Consider the following operation on two account-scoped documents that each represent an account member:
A common reason for preparing
DocumentReference
s like these is to use them in atransaction
or abatch
write operation, sometimes both:As of today, we have no way to assert directly that "the document at path
`accounts/${accountId}/users/${userId}`
was updated with{ this data }
". Today's methods, which can easily confuse assertions onuserRef
with assertions onotherUserRef
, rely on the following assertions in Jest:mockCollection
was called four times, twice with the argument"accounts"
and twice with the argument"users"
. We assert this using fourtoHaveBeenNthCalledWith
assertions, and onetoHaveBeenCalledTimes
assertion for good measure.mockDoc
was called four times, once with the argumentaccountA
, once with the argumentaccountB
, and twice with the argumentuserId
. We assert this in the same verbose manner as we didmockCollection
.mockUpdateTransaction
was called once with the first argument being an instance ofFakeFirestore.DocumentReference
and the second being{ the data }
. To assert more than this requires reconstructing the same document reference using the mocked Firestore database (which may involve animport
or arequire
). This is a nontrivial operation.We cannot clearly assert the order of calls to
mockCollection
andmockDoc
without doing a fancy dance about which call with which arguments came before which other call with which arguments, which will always be complicated by the fact that we callcollection("accounts")
twice. We may simplify this call, but the issue remains about the ordering of the other calls. Test cases quickly become very bulky and difficult to maintain.Proposed Solution
Asserting specific document paths can be done with a simple Jest matcher!
FakeFirestore.DocumentReference
has apath
property which contains a string similar to the one canonical toFirestore.DocumentReference
. In theory, we all we need to do to assert that atransaction.update
call occurred on the correct path is to assert that the call's first argument has apath
property and that its value is the expected document path.We may define the matcher with something like the following TypeScript code:
This code uses the
path
property ofFakeFirestore.DocumentReference
orFakeFirestore.CollectionReference
to check that the value under test is a child of the provided path.We use the matcher in test cases like so:
With one simple assertion, we can prove that the unit accessed some document under
accounts/accountA
. It is now trivial to assert other important parts of the Firestore call, such as:Other matchers may be written to handle more granular or more specific cases as needed.
Further Work
FakeFirestore.DocumentReference#path
The canonicalFirestore.DocumentReference
object has apath
property, but in designingFakeFirestore.DocumentReference
I did not consider the structure of the canon version. This may break some code that relies on the value of thatpath
property. We should update our implementation to match Firestore's.#102 makes document paths more closely match Firestore's implementation to the best of my knowledge.
What about single I/O calls?
The matcher described in this proposal only extend Jest's present ability to assert properties of arguments to mock function calls. As far as I am aware, Jest does not have an easy way to assert properties of objects on which mock methods were called. Some restructuring may be necessary to permit that capability in a similar fashion. Suggestions would be appreciated.
EDIT: IDEA!! IIRC, Firestore canonically returns a document ref or document snapshot as the result of a write operation. We might be able to assert the path of that return value in a Jest matcher.
The text was updated successfully, but these errors were encountered: