Skip to content

Global setup file for native node test runner #49732

Description

@jamm3e3333

What is the problem this feature will solve?

I'm sorry if there's already such a feature and I haven't found a way how to solve this. Basically what I'm looking for is to run some function that would run beofre all the tests and setup the environment. So far the only way how to setup some environment is doing it with the run function but that is only limited for each test suite from what I've tried. I would love to have the have some setup file in the way how does it work for example in jest or mocha or other testing framework.

What is the feature you are proposing to solve the problem?

Have a setup.js/setup.ts file that will run before all the tests, you would somehow specify in command that starts the tests this setup file.

What alternatives have you considered?

node version: v20.6.1
npm version: 9.8.1

I've tried this setup file

setup.ts

import { performance } from 'perf_hooks'
import { run } from 'node:test'

run({
    files: ['src/node-test-runner/suite.test.ts'],
    setup: () => {
        let memStats: Record<string | number, number> = {};
        const perf = performance;

        const start = perf.now();
        const cpuUsage = process.cpuUsage()

        process.nextTick(() => {
            const current = process.memoryUsage();
            for (const key in current) {
              memStats[key] = Math.max(memStats[key] ?? 0, current[key as keyof ReturnType<typeof process.memoryUsage>]);
            }
          });

          process.on("exit", () => {
            logger.info({
              memStats,
              timeMilliseconds: perf.now() - start,
              cpuUsage: process.cpuUsage(cpuUsage),
              pid: process.pid,
            });
          });
    },
})

suite.test.ts

import { describe, it } from 'node:test'
import {equal} from 'node:assert'
import { testBody } from '../index'
import { logger } from '../logger'

const TEST_ITERATIONS = Number(process.env.TEST_ITERATIONS);

describe(__filename, () => {
    it(`${__filename}`, async () => {
            equal(true, true)
    })
})

and I'm running this command to execute tests: node --require ts-node/register --test

but neither the setup.ts file is executed nor the suite.test.ts file is executed

Activity

  1. koshic commented on Sep 20, 2023

    @koshic

    You should run it via 'node --require ts-node/register setup.ts'.

    I use similar solution - kind of wrapper around 'run' function to setup different reporters (CI / local run), collect coverage with c8, globs for .ts files, additional loaders, etc. Because this wrapper is a package with 'bin' script named 'test', I can run tests via 'yarn test'.

    Also, adding your own config (as an example, to exclude some files from coverage) is a few lines:

    // wrapper
    let config;
    try {
     config = await import('test.config.js');
    } catch {
     config = { /* default */ };
    }
    
    run(/* your test config */)
    
    // config
    export default {
     // anything you may need
    }
  2. benjamingr commented on Sep 21, 2023

    @benjamingr
    Member

    Yeah this is just a syntax thing, node --require ts-node/register setup.ts as it invokes the runner programatically.

    If you build useful stuff on top of the native test runner or notice missing features please do tell us and put it on npm if you're comfortable with it being open source :)

  3. koshic commented on Sep 21, 2023

    @koshic

    If you build useful stuff on top of the native test runner or notice missing features please do tell us and put it on npm if you're comfortable with it being open source :)

    In my case it's an internal and very specific package created to keep some (a lot of...) defaults inside. I believe when we get native coverage reporter (with source maps & lcov output) and globs, such wrappers will be redundant for most of us. Only one thing may be really required - some way to organize reporters (but we already have .env support, right?).

  4. benjamingr commented on Sep 23, 2023

    @benjamingr
    Member

    Right, and since the programatic invocation is just a Node.js file you can use whatever you want for config there :)

  5. jamm3e3333 commented on Sep 23, 2023

    @jamm3e3333
    Author

    Yeah this is just a syntax thing, node --require ts-node/register setup.ts as it invokes the runner programatically.

    If you build useful stuff on top of the native test runner or notice missing features please do tell us and put it on npm if you're comfortable with it being open source :)

    Sure I'll try to come up with something

  6. rluvaton commented on Sep 28, 2023

    @rluvaton
    Member

    Reopening as you also can't do global teardown

  7. rluvaton commented on Sep 28, 2023

    @rluvaton
    Member

    actually you can do:

    const {tap} = require('node:test/reporters');
    const process = require('node:process');
    const path = require("node:path");
    const {run} = require("node:test");
    const {finished} = require('node:stream');
    
    const stream = run({
        files: ['./src/a.test.js', './src/b.test.js'],
        concurrency: false,
    
        setup: () => {
            console.log('setup');
        }
    })
        .compose(tap);
    
    finished(stream, () => {
        console.log('teardown');
    });
    
    stream.pipe(process.stdout);

    But it's only for when using run I think we should document it though

  8. cjihrig commented on Sep 28, 2023

    @cjihrig
    Contributor

    There has been some discussion about Node.js as a whole supporting a configuration file. I'm not sure if that will lead to anything, but having a separate file for just the test runner would not be great.

  9. added
    test_runnerIssues and PRs related to the test runner subsystem.
    on Sep 28, 2023
  10. ernestdolog commented on Nov 14, 2023

    @ernestdolog

    But it's only for when using run I think we should document it though

    Would be awesome to have it documented. What would be even better is to have it working with some sort of file detection, like: files: [Application.baseDir + '/__tests__/**/*.test.ts'],

    My global test setup is the following:

    /**
     * The Global Test Runner
     * =========
     * Provides global test setup.
     *
     * Because of node test runners concurrency this is elemental.
     */
    import './framework.setup';
    import {run} from 'node:test';
    import process from 'node:process';
    import {tap} from 'node:test/reporters';
    import {Application} from '#app/application';
    
    const stream = run({
        files: [
            Application.baseDir + '/__tests__/e2e/auction-api.resolver.test.ts',
            Application.baseDir + '/__tests__/e2e/user-api.resolver.test.ts',
            Application.baseDir + '/__tests__/unit/logger.unit.test.ts',
        ],
        /**
         * As after integration, and e2e tests we truncate databases
         * This should not happen paralelly, unless we figure a way to only delete the data added at that test with a one-liner
         * Switch on concurrency locally for unit tests and such
         */
        concurrency: false,
    }).compose(tap);
    
    stream.pipe(process.stdout);
    

    I have a couple of test suites coming. Would be nice, if all tests unit-integration-e2e could be imported with a one-liner.

  11. 16 remaining items

  12. Fenykepy commented on Mar 12, 2025

    @Fenykepy

    So as mentioned in #54675, in Node 22.9 I managed to achieve globalSetup and globalTeardown feature by using test isolation none and before hook

    node --import ./globalHooks.mjs --experimental-test-isolation=none --test 'src/**/*.test.js' globalHooks.mjs`

    import { after, before } from 'node:test';

    before(async () => {
    await startDockerContainers();
    });

    after(async () => {
    await closeDatabaseConnections();
    await stopDockerContainers();
    });
    Works as expected 🎉 also with TypeScript (I need to test TS with ESM more though)

    Does it mean that adding globalSetup and globalTeardown hooks to node:test is not needed anymore? I would say not necessarily. Since process is the default isolation mode, I guess people may still have a need for them. What do you think?

    This doesn't work as expected with async setup when before is used in other tests modules:

    tsx --import ./tests/node/globalHooks.ts --experimental-test-isolation=none --test './**/*.test.{js,ts}'
    

    globalHooks.ts

    import { after, before } from 'node:test';
    
    async function setupDatabase() {
        // some async stuff
    };
    
    async function seedDatabase() {
      // some async stuff
    };
    
    async function closeDatabaseConnection() {
      // some async stuff
    };
    
    before(async () => {
      console.log('before all');
      await setupDatabase();
      await seedDatabase();
      console.log('database ready');
    });
    
    after(async () => {
      console.log('after all');
      await closeDatabaseConnection();
      console.log('connection closed');
    });
    

    someFunction.test.ts

    import { after, before, suite, test } from 'node:test';
    
    async function createSomeDatabaseObjects() {
      // async stuff, must run after resetDatabase()
    };
    
    before(async () => {
      console.log('before suite');
      await createSomeDatabaseObjects(); // fails, database isn't ready yet
      console.log('database objects created');
    });
    
    after(async () => {
      console.log('after suite');
    });
    
    suite("Suite of someFunction", () => {
      test("someFunction should work as expected", () => {
        // some tests
      });
    });
    

    Output:

    before all
    before suite // should run after database ready
    database ready
    after all
    connection closed
    after suite // should run before after all
    

    Any idea? (for moment, I'm avoid using before and after in tests modules but it's quite annoying...)

  13. Fenykepy commented on Mar 12, 2025

    @Fenykepy

    I found that wrapping whole module in a main suite works, albeit it's nowhere written in docs that sub-suites are allowed.

    someFunction.test.ts

    import { after, before, suite, test } from 'node:test';
    
    async function createSomeDatabaseObjects() {
      // async stuff, must run after resetDatabase()
    };
    
    suite("Suite of module someFunction", () => {
      before(async () => {
        console.log('before suite');
        await createSomeDatabaseObjects(); // fails, database isn't ready yet
        console.log('database objects created');
      });
    
      after(async () => {
        console.log('after suite');
      });
    
      suite("Suite of someFunction", () => {
        test("someFunction should work as expected", () => {
          // some tests
        });
      });
    
      // other suites and tests
    });
    

    Output:

    before all
    database ready
    before suite // ok
    after suite // ok
    after all
    connection closed
    
  14. github-actions commented on Sep 9, 2025

    @github-actions
    Contributor

    There has been no activity on this feature request for 5 months. To help maintain relevant open issues, please add the never-stale Issues and PRs exempt from automated stale handling. label or close this issue if it should be closed. If not, the issue will be automatically closed 6 months after the last non-automated comment.
    For more information on how the project manages feature requests, please consult the feature request management document.

  15. added
    staleIssues and PRs marked stale due to inactivity and scheduled for automatic closure.
    on Sep 9, 2025
  16. removed
    staleIssues and PRs marked stale due to inactivity and scheduled for automatic closure.
    on Sep 9, 2025
  17. doberkofler commented on Sep 10, 2025

    @doberkofler

    @Fenykepy

    I found that wrapping whole module in a main suite works, albeit it's nowhere written in docs that sub-suites are allowed.

    This would clearly be be most convenient way to solve this if all tests are in a single source file but in my use case i have multiple files that expect some resources to be available and later be be disposed.

  18. doberkofler commented on Sep 10, 2025

    @doberkofler

    I resolved my use case by using something like node --import ./tests/global_hooks.ts --experimental-test-isolation=none --test --test-concurrency=1 "./tests/**/*.test.ts" but still believe that Node.js could offer a better DX by offering a configuration option to explicitly define setup and teardown like other test runner:

  19. pmarchini commented on Sep 11, 2025

    @pmarchini
    Member

    I resolved my use case by using something like node --import ./tests/global_hooks.ts --experimental-test-isolation=none --test --test-concurrency=1 "./tests/**/*.test.ts" but still believe that Node.js could offer a better DX by offering a configuration option to explicitly define setup and teardown like other test runner:

    Hey @doberkofler actually we now provide https://nodejs.org/api/test.html#global-setup-and-teardown!

  20. Twipped commented on Sep 12, 2025

    @Twipped

    @pmarchini that's great, thank you, but it also means yet another flag I'm having to include in my test command just to get the suite to work. Has anyone talked about a config file for all this stuff?

  21. pmarchini commented on Sep 13, 2025

    @pmarchini
    Member

    Hey @Twipped, we’ve introduced a new experimental feature: https://nodejs.org/api/cli.html#--experimental-config-fileconfig
    .
    With the experimental config file, it's possible to avoid setting all the flags via the CLI.

  22. Twipped commented on Sep 15, 2025

    @Twipped

    @pmarchini excellent, thank you!

  23. alxmrtn commented on Nov 21, 2025

    @alxmrtn

    @pmarchini I've been trying to get tests to run using the globalSetup and globalTeardown functions, and I've noticed that globalTeardown sometimes begins to execute before all test files are finished executing without--test-concurrency=1 set. Is this a known issue?

    edit. adding example:
    Image

  24. pmarchini commented on Nov 21, 2025

    @pmarchini
    Member

    Hey @alxmrtn, thanks for reporting it!
    Could I please ask you to verify whether it's only a matter of output, or if the teardown actually happens before all tests complete?

    e.g., I would expect test failures if the DB really gets wiped before the completion of dependent tests.

    (I've noticed issues with the order of the printed output before, so I'm wondering if that's the case!)

    Note: I'm closing this issue, as the feature request has been completed.
    If what you're reporting is actually a bug, I will ask you to open an issue with a minimal repro

  25. alxmrtn commented on Nov 21, 2025

    @alxmrtn

    @pmarchini let me add some more db heavy tests and get back to you. if I keep seeing it I'll open up an issue - thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature requestIssues requesting new Node.js features.test_runnerIssues and PRs related to the test runner subsystem.

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions