This is a fork of @glennsl/rescript-jest that aims to provide bindings for vitest.
Pre-alpha.
I am aware that there is already a rescript-vitest repository. However, I wanted to create a library with the same bindings as rescript-jest to make it easier to migrate existing tests from Jest to Vitest.
I also appreciate the bindings provided by rescript-jest, particularly because each test concludes with a single assertion, which I find more suitable for functional programming.
- The top-level module is named
Vitestinstead ofJest. - The module
Jestis renamed toVi. - The module
JestJs, containing the mock functions, is renamed toMock. - The following functions are deprecated:
runAllImmediates: not supported by Vitest.testAsync,beforeAllAsync,beforeEachAsync,afterAllAsync,afterEachAsync: deprecated.
If you have existing tests using rescript-jest, you can easily migrate them to rescript-vitest by changing the module names.
If you have any async tests, you will need to change the testAsync, beforeAllAsync, beforeEachAsync, afterAllAsync, and afterEachAsync functions to testPromise, beforeAllPromise, beforeEachPromise, afterAllPromise, and afterEachPromise. If you use runAllImmediates, you will need to port the tests to use runAllTimers instead. (???)
Some new features have been added that are not present in rescript-jest.
affirmfunction to allow multiple assertions in a single testdescribePromisefunction to allow async setup in describe blocks
affirm used to be an internal function, but now is made public to allow multiple assertions in a single test. In some cases this is appropriate, when having two sequential tests that depend on each other. Such tests are often needed in end-to-end tests with Cypress, Playwright, or Puppeteer. Note that this could actually be split up to two separate tests, but it would require the usage of mutable variables (refs), and would result in tests depending on each other, which is against the testing philosophy, where tests should be independent of each other.
test("multiple assertions", () => {
let a = 1
expect(a)->toEqual(1)->affirm
let a = a + 1
expect(a)->toEqual(2)->affirm
expect("This is the final, fine expectation")->toBe("This is the final, fine expectation")
})
describePromise is supported by vitest, so we add it here.
describePromise enables asynchronous setup directly inside describe blocks.
This is useful when you need to prepare something before tests run—for example,
fetching data from a server or reading files.
Functionally, it works the same as using a beforeAllPromise block, but putting
the setup inside the describe is often more convenient since you don’t need to
use mutable variables (refs) to share data between the setup and the tests.
Note: describe blocks always execute before any setup handlers.
describePromise("async setup", async () => {
let data = await fetchData()
"some test"->test(() => expect(data)->toEqual(expectedData))
})Similarly to testPromise, describePromise can take an optional timeout argument.
describePromise("async setup with timeout", ~timeout=5000, async () => {
let data = await fetchData()
"some test"->test(() => expect(data)->toEqual(expectedData))
})This package is tested with Rescript 11 and 12.
describePromise("async setup with timeout", ~timeout=5000, async () => {
let data = await fetchData()
expect(data)->toEqual(expectedData)
})This package is tested with Rescript 11 and 12.
You can install the library using npm or yarn:
npm install --save-dev @greenfinity/rescript-vitestor
yarn add --dev @greenfinity/rescript-vitestThen add @greenfinity/rescript-vitest to bs-dev-dependencies in your rescript.json (or bsconfig.json):
{
...
"bs-dev-dependencies": ["@greenfinity/rescript-vitest"]
}Then add __tests__ to sources in your rescript.json (or bsconfig.json):
"sources": [
{
"dir": "src"
},
{
"dir": "__tests__",
"type": "dev"
}
]You can look at the tests in the __tests__ directory for examples.
The rest of this page is the unchanged README.md from the original rescript-jest repository.
NOTE: The NPM package has moved to @glennsl/rescript-jest. Remember to update both package.json AND bsconfig.json.
- bs-jest is rebranded as rescript-jest
- rescript-jest depends on Rescript 9.1.4, Jest 27.3.1 and @ryyppy/rescript-promise 2.1.0.
- Starting from Jest 27.0.0 jest-jasmine was replaced by jest-circus changing the semantics for before and after hooks.
afterAllAsyncandafterAllPromisehooks now time-out consistent with the behavior ofbeforeAllAsyncandbeforeAllPromisein version 0.7.0 of bs-jest.beforeAllAsyncandbeforeAllPromisealso now behave consistently with 'afterAllAsyncandafterAllPromisewhen included in skipped test suites. - rescript-jest API now uses data-first semantics throughout and uses
rescript-promisein place ofJs.Promise. - usefakeTimers() binding updated to address changes in the Jest fake timer API (useFakeTimer(~implementation=[#legacy|#modern], ()))
- Deprecated BuckleScript
@bs.send.pipebindings were converted to rescript@sendbindings. - All tests have been updated to reflect semantic and behavioral changes.
- Babel modules have been added as dev dependencies to make generated bs-jest bindings available in ES6 module format.
- Babel and Jest config files are included illustrating how to transform ES6 modules for Jest.
To generate ES6 bindings for your project, update bsconfig.json
"suffix": ".mjs",
"package-specs": {
"module": "ES6",
"in-source": true
},Then add @babel/core, @babel/preset-env and babel-jest packages to your project. Also, add babel.config.js
module.exports = {
presets: [
['@babel/preset-env',
{targets: {node: 'current'}}
]
],
"plugins": []
}Finally, add minimal jest.config.js
module.exports = {
moduleFileExtensions: [
"js",
"mjs",
],
testMatch: [
"**/__tests__/**/*_test.mjs",
"**/__tests__/**/*_test.bs.js",
],
transform: {
"^.+\.m?js$": "babel-jest"
},
transformIgnorePatterns: [
"node_modules/(?!(rescript)/)"
], Update testMatch, transform and transformIgnorePatterns settings depending on where your tests are stored, and other dependenies of your project that may need to be transformed to ES6 format.
Most of what's commonly used is very stable. But the more js-y parts should be considered experimental, such as mocking and some of the expects that don't transfer well, or just don't make sense for testing idiomatic Reason/OCaml code but could be useful for testing js interop.
- Global: Fully implemented and tested, apart from
require.* - Expect: Mostly implemented. Functionality that makes sense only for JS interop have been moved to
ExpectJs. Some functionality does not make sense in a typed language, or is not possible to implement sensibly in Rescript. - Mock Functions: Experimental and unsafe implementation, very much in flux. The Jest bindings will most likely be relegated to the
MockJsmodule as it's very quirky to use with native code. A separate native from-scratch implementation might suddenly appear asMock. - The Jest Object: Fake timers are fully implemented and tested. Mock functionality has been moved to
JestJs. It's mostly implemented, but experimental and largely untested. - Snapshotting: Expect functions exist and work, but there's currently no way to implement custom snapshot serializers.
open Jest;
describe("Expect", () => {
open Expect;
test("toBe", () =>
expect(1 + 2) -> toBe(3))
});
describe("Expect.Operators", () => {
open Expect;
open! Expect.Operators;
test("==", () =>
expect(1 + 2) === 3)
}
);
See the tests for more examples.
npm install --save-dev @glennsl/rescript-jestor
yarn install --save-dev @glennsl/rescript-jestThen add @glennsl/rescript-jest to bs-dev-dependencies in your bsconfig.json:
{
...
"bs-dev-dependencies": ["@glennsl/rescript-jest"]
}Then add __tests__ to sources in your bsconfig.json:
"sources": [
{
"dir": "src"
},
{
"dir": "__tests__",
"type": "dev"
}
]Put tests in a __tests__ directory and use the suffix *test.res/ (Make sure to use valid module names. e.g. <name>_test.res is valid while <name>.test.res is not). When compiled they will be put in a __tests__ directory under lib, with a *test.bs.js suffix, ready to be picked up when you run jest. If you're not already familiar with Jest, see the Jest documentation.
One very important difference from Jest is that assertions are not imperative. That is, expect(1 + 2) -> toBe(3), for example, will not "execute" the assertion then and there. It will instead return an assertion value which must be returned from the test function. Only after the test function has completed will the returned assertion be checked. Any other assertions will be ignored, but unless you explicitly ignore them, it will produce compiler warnings about unused values. This means there can be at most one assertion per test. But it also means there must be at least one assertion per test. You can't forget an assertion in a branch, and think the test passes when in fact it doesn't even test anything. It will also force you to write simple tests that are easy to understand and refactor, and will give you more information about what's wrong when something does go wrong.
At first sight this may still seem very limiting, and if you write very imperative code it really is, but I'd argue the real problem then is the imperative code. There are however some workarounds that can alleviate this:
- Compare multiple values by wrapping them in a tuple:
expect((this, that)) -> toBe((3, 4)) - Use the
testAllfunction to generate tests based on a list of data - Use
describeand/orbeforeAllto do setup for a group of tests. Code written in Rescript is immutable by default. Take advantage of it. - Write a helper function if you find yourself repeating code. That's what functions are for, after all. You can even write a helper function to generate tests.
- If you're still struggling, make an issue on GitHub or bring it up in Discord. We'll either figure out a good way to do it with what we already have, or realize that something actually is missing and add it.
For the moment, please refer to Jest.resi.
- bs-jest-dom - Custom matchers to test the state of the DOM
If you encounter the error SyntaxError: Cannot use import statement outside a module, it may be that you are mixing es6 and commonjs modules in your project. For example, this can happen when you are building a React project since React builds are always in ES6. To fix this, please do the following:
-
Make sure your
bsconfig.jsoncompiles"es6"or"es6-global":"package-specs": { "module": "es6", }
-
Install esbuild-jest through
yarnornpmas adevDependency. -
Build your Rescript project with deps:
rescript build -with-deps. -
Add this to your Jest config (or
jestof yourpackage.json):{ "transform": { "^.+\\.jsx?$": "esbuild-jest" }, "transformIgnorePatterns": ["<rootDir>/node_modules/(?!(rescript|@glennsl/rescript-jest)/)"] } -
The property
"transformIgnorePatterns"is an array of strings. Either you do some regex or organize them in an array. Please make sure all folders innode_modulesinvolving compiled .res/.ml/.re files and the like such asrescriptor@glennsl/rescript-jestare mentioned in the aforementioned array.
This problem is also addressed in Issue #63.
git clone https://github.com/glennsl/rescript-jest.git
cd rescript-jest
npm installThen build and run tests with npm test, start watchers for rescriptand jest with npm run watch:rescript and npm run watch:jest respectively. Install screen to be able to use npm run watch:screen to run both watchers in a single terminal window.
- [BREAKING] Bump required rescript to 11.1.x, uncurried mode requires MockJs.fn to be applied with explcicit currying.
- Worked around bug in rescript 11 curried mode where
@uncurrycauses an extra param which changes Jest behaviour, by replacing@uncurrywith(. ).
- [BREAKING] Bump required rescript to 10.1.x
- Remove unnecessary dependency on
@ryyppy/rescript-promise
- Added
testAllPromise.
- Added
Jest.setSystemTime.
- [BREAKING] Removed the unnecessarily verbose generated namespace.
- Moved repository from
glennsl/bs-jesttoglennsl/rescript-jest - Renamed published package to
@glennsl/rescript-jest - [BREAKING] Converted source code to ReScript, hence will no longer work with versions of BuckleScript that lack ReScript support.
- [BREAKING] As of Jest 27.0.0, Jest-Circus replaces Jest-Jasmine by default leading to change in behavior of async and Promise before and after hooks.
- [BREAKING] As the
|>operator is deprecated in Recript 9.x, all APIs now use data-first (->) semantics.
- [BREAKING] Actually removed
toThrowException,toThrowMessageandtoThrowMessageReas they relied on assumptions about BuckleScript internals that no longer hold.
- Added
Expect.toContainEqual - Updated to Jest 26.5.2
- Upgraded bs-platform to 8.3.1
- Added
Expect.toMatchInlineSnapshot
- Updated to Jest 25.1.0
- Added
Todo.test
- Updated jest to 24.3.1
- Fixed jest warnings not to return anything from
describecallbacks by explicitly returningundefined(otherwise BuckleScript will return something else like(), which is represented as0) - Fixed several newly uncovered uncurrying issues caused by surprise breaking changes in BuckleScript (Thanks again, Bob!)
- Added
Jest.advanceTimersByTime, which is basically just an alias ofJest.runTimersToTime
- Added
Expect.not__for transitional compatibility with Reason syntax change of "unkeywording"notby mangling it intonot_, andnot_intonot__and so on.
- Made uncurrying explicit for
afterAllPromisetoo.
- Made uncurrying explicit to fix a breaking change in implicit uncurrying from
bs-platform4.0.7 (Thanks Bob!)
- Removed some optimizations on skipped tests that Jest 23 suddenly started objecting to (#30)
- Added
MockJs.new0,new1andnew2 - Added
timeoutargument totestAsyncandtestPromisefunctions - Added
beforeEachAsync,beforeEachPromise,afterEachAsyncandafterEachPromise - Added
beforeAllAsync,beforeAllPromise,afterAllAsyncandafterAllPromise
- Moved repository from
reasonml-community/bs-jesttoglennsl/bs-jest - Renamed NPM package from
bs-jestto@glennsl/bs-jest
- Added
toThrowException - Fixed an issue with custom Runner implementation shadowing the global
testfunction from jest - Fixed a typo in the js boundary of
not_ |> toBeLessThanEqual
- Removed deprecations
- Added
testAll,Only.testAll,Skip.testAllthat generates tests from a list of inputs - Fixed type signature of
fail - Added
expectFn