jstests/README.md
At MongoDB we write integration tests in JavaScript. These are tests written to exercise some behavior of a running MongoDB server, replica set, or sharded cluster. This guide aims to provide some general guidelines and best practices on how to write good tests.
It is best to use variable names that attempt to describe what a value is used for. For example, naming a variable that stores a collection named collectionToDrop is much better than just naming the variable collName.
All assertions in a test should attempt to verify the most specific property possible. For example, if you are trying to test that a certain collection exists, it is better to assert that the collection’s exact name exists in the list of collections, as opposed to verifying that the collection count is equal to 1. The desired collection’s existence is sufficient for the collection count to be 1, but not necessary (a different collection could exist in its place). Be wary of adding these kind of indirect assertions in a test.
Your JS test will likely be running with many other files before and after it. It's important to start from a known state, and to restore that state (to a reasonable extent) at the end of your test content.
@tags instead of just an early-return. This will avoid the test being scheduled in the first place if the environment is not supported.--continueOnFailure flag is used in CI, so the fixture is shared across many test files, and is only torn down at the end.
after hooks in your test content.We have fully migrated to the modularized JavaScript world so any new test should use modules and adapt the new style.
It's always important to keep the test context clean so we should only import/export what we need.
In the past, we have seen tests referring some "undeclared" or "redeclared" variables, which are actually introduced through load(). Now with modules, the scope is more clear. We can use global variables properly to setup the test and don't need to worry about polluting other tests.
To avoid naming conflicts, we should not make the name of exported variables too general which could easily conflict with another variable from the test which import your module. For example, in the following case, the module exported a variable named alphabet and it will lead to a re-declaration error.
import {alphabet} from "/matts/module.js";
const alphabet = "xyz"; // ERROR
let/const should be preferred over var since these can help detect double declaration at the first place. Like, in the naming conflict example, if the second line is using var, it could easily mess up without throwing an error.
Due to legacy, we have a lot of code that is using the old style to do export, like the following.
const MyModule = (function() {
function myFeature() {}
function myOtherFeature() {}
return {myFeature, myOtherFeature};
})();
Instead, we should use the ES6 way to do export, as follows.
export function myFeature() {}
export function myOtherFeature() {}
// When import from test
import * as MyModule from "/path/to/my_module.js";
This can help the language server to discover the methods and provide code navigation for it.
The mochalite.js library ports over a subset of MochaJS functionality for the shell, including:
it test contructiondescribe suite structuresit.only and describe.only to run only those suites and testsit.skip and describe.skip to skip those suites and testsbefore and after hooks, to run once around all it testsbeforeEach and afterEach hooks, to run around each it testdescribe variants) also support async functions--mochagrep flag, which mirrors the grep flag from MochaJSExample using several APIs:
import {
after,
afterEach,
before,
beforeEach,
describe,
it,
} from "jstests/libs/mochalite.js";
describe("simple inserts and finds", () => {
before(() => {
this.fixtureDB = startupNewDB();
});
beforeEach(() => {
this.fixtureDB.seed();
});
afterEach(async () => {
await this.fixtureDB.clear();
});
after(() => {
this.fixtureDB.shutdown();
});
it("should do something", () => {
this.fixtureDB.insert({name: "test"});
assert.eq(this.fixtureDB.find({name: "test"}).count(), 1);
});
it("should error on invalid data", () => {
const e = assert.throws(() =>
this.fixtureDB.insert({notafield: undefined}),
);
assert.eq(e.message, "Field 'notafield' not found");
});
});
Use it.only to run just the one test (eg. during debugging):
it.only("should do something", () => {
this.fixtureDB.insert({name: "test"});
assert.eq(this.fixtureDB.find({name: "test"}).count(), 1);
});
or use the filter from resmoke to avoid any file edits:
buildscripts/resmoke.py run --suites=no_passthrough --mochagrep "do something" jstests/noPassthrough/mytest.js
JS Test files can leverage "tags" that suites can key off of to include and/or exclude as necessary. Not scheduling a test to run is much faster than the test doing an early-return when preconditions are not met.
The simplest use case is having something like the following at the top of your js test file:
/**
* Tests for the XYZ feature
* @tags: [requires_fcv_81]
*/
See tags.md for more details.