-
Notifications
You must be signed in to change notification settings - Fork 886
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
Throws "FirebaseError: Data must be an object, but it was: a custom Object object" when unit testing firestore rules. #7781
Comments
Hi @ASE55471 thanks for the detailed report of the issue. I'll try and replicate and validate the issue. |
Hi @ASE55471, I was able to reproduce the behavior now. Let me check what we can do for this issue or bring someone here that can provide more context about it. I’ll update this thread if I have any information to share. |
Update: I've figured out where things are going wrong, but don't have a solution yet. The error is happening because a check for firebase-js-sdk/packages/firestore/src/util/input_validation.ts Lines 95 to 102 in 00235ba
which is called from The strange thing is that if I add console.log(Object.getPrototypeOf(profile)===Object.prototype); to the Jest test code in |
I've found the root cause and a solution. Basically, the code that runs in import {
RulesTestEnvironment,
initializeTestEnvironment,
} from "@firebase/rules-unit-testing"
import { afterAll, beforeAll, describe, test } from "@jest/globals";
import * as fs from "fs";
let testEnv: RulesTestEnvironment;
beforeAll(async () => {
testEnv = await initializeTestEnvironment({
projectId: "my-project-id",
firestore: {
rules: fs.readFileSync("firestore.rules", "utf8"),
host: "127.0.0.1",
port: 8080,
},
});
});
afterAll(async () => {
if (testEnv) {
await testEnv.cleanup();
testEnv = null;
}
}); With this change, both the Here is a more detailed explanation of what's going on. Jest attempts to isolate tests from one another, and one of the mechanisms it uses for this is Node's "vm" feature (https://nodejs.org/docs/latest-v21.x/api/vm.html). By using the "vm" module, Node can run two pieces of JavaScript code with distinct global variables. This reduces the degree to which tests can interfere with each other. For example, if one test adds something to Object's prototype then that change would normally bleed into other tests; however, since the tests are run in a different "vm", they both have their own local copy of the Object class, so one test's changes to it are not reflected in the other test. I found this article that explains it better than I can: https://backend.cafe/should-you-use-jest-as-a-testing-library and it also provides some additional workarounds. The reason that this causes a problem is that Firestore attempts to do some checks of the objects specified to Another workaround that seems to work, but is likely more brittle, is to export a function from the test environment to "reparent" an object into the "vm" of Firestore. The tests would, then, need to call this method on all objects specified to // test-environment.ts
import { RulesTestEnvironment, initializeTestEnvironment } from "@firebase/rules-unit-testing";
import type { DocumentData } from "firebase/firestore";
import * as fs from 'fs';
const NodeEnvironment = require('jest-environment-node').TestEnvironment;
const MY_PROJECT_ID = "my-test-project";
declare global {
var testEnv: RulesTestEnvironment;
var reparent: (obj: DocumentData) => DocumentData;
}
class CustomEnvironment extends NodeEnvironment {
constructor(config: any, context: any) {
super(config, context);
console.log(config.globalConfig);
console.log(config.projectConfig);
this.testPath = context.testPath;
this.docblockPragmas = context.docblockPragmas;
}
async setup() {
await super.setup();
let testEnv = await initializeTestEnvironment({
projectId: MY_PROJECT_ID,
firestore: {
rules: fs.readFileSync("firestore.rules", "utf8"),
host: '127.0.0.1',
port: 8080
},
});
this.global.testEnv = testEnv;
this.global.reparent = function reparent(obj: unknown): unknown {
if (obj === null) {
return null;
}
if (typeof obj === "undefined") {
return undefined;
}
if (typeof obj !== "object") {
return obj;
}
if (Array.isArray(obj)) {
const reparentedObj = [];
for (const element of obj) {
reparentedObj.push(reparent(element));
}
return reparentedObj;
}
const reparentedObj: Record<string|number|symbol, any> = {};
for (const propertyName in obj) {
reparentedObj[propertyName] = reparent(obj[propertyName]);
}
return reparentedObj;
}
}
async teardown() {
this.global.testEnv?.cleanup();
await super.teardown();
}
getVmContext() {
return super.getVmContext();
}
}
module.exports = CustomEnvironment; // tests/index.test.ts
// This is the same as the code in the OP of this issue EXCEPT that the call to
// profileRef.set(profile) is changed to profileRef.set(reparent(profile))
import {
RulesTestEnvironment,
} from "@firebase/rules-unit-testing"
import { afterAll, beforeAll, describe, test } from "@jest/globals";
import type { DocumentData } from "firebase/firestore";
let testEnv: RulesTestEnvironment;
let reparent: (obj: DocumentData) => DocumentData;
beforeAll(async () => {
testEnv = globalThis.testEnv;
reparent = globalThis.reparent;
})
afterAll(async () => {
await testEnv?.clearFirestore();
})
describe("app", () => {
test("Example", async () => {
// Create base data for test.
const profile = {
name: "Jason"
};
// Write prepared data to database for test.
await testEnv.withSecurityRulesDisabled(async (context) => {
const firestore = await context.firestore();
const profileRef = firestore.collection("profile").doc("user");
await profileRef.set(reparent(profile));
});
});
}) Since there is no action for the Firestore SDK to take to fix this issue, I'm going to close it. Please feel free to comment if you think there is something that the Firestore SDK could/should do to mitigate this issue. Thank you for reporting it! All the best. |
Operating System
MacOS 14.1 Sonoma
Browser Version
Firebase SDK Version
Firebase SDK Product:
Firestore
Describe your project's tooling
firebase-tools: 12.8.1
Describe the problem
I am testing firestore rules with custom node environment, But when I write data emulator always throws
FirebaseError: Function DocumentReference.set() called with invalid data. Data must be an object, but it was: a custom Object object
,Steps and code to reproduce issue
npm run jest
The text was updated successfully, but these errors were encountered: