node icon indicating copy to clipboard operation
node copied to clipboard

In source / inline tests

Open olalonde opened this issue 10 months ago • 6 comments

What is the problem this feature will solve?

Sometimes you want to test a small utility function and would like to colocate the tests inside the same file as the library instead of using a separate file.

import assert from "node:assert/strict";
import test from "node:test";

export const ping = () => "pong";

// ping implementation and its tests live in the same file
test("ping", (_t) => {
  assert.equal(ping(), "pong");
});

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

vitest solves this problem by having the test runner add a property to import.meta and using it to conditionally run tests.

https://vitest.dev/guide/in-source.html

// src/index.js

// the implementation
export function add(...args) {
  return args.reduce((a, b) => a + b, 0)
}

// in-source test suites
if (import.meta.vitest) {
  const { it, expect } = import.meta.vitest
  it('add', () => {
    expect(add()).toBe(0)
    expect(add(1)).toBe(1)
    expect(add(1, 2, 3)).toBe(6)
  })
}

I am not sure if it's the best way to achieve this but it's one way.

What alternatives have you considered?

No response

olalonde avatar Jul 28 '23 22:07 olalonde

This has come up before and did not get an enthusiastic response: https://github.com/nodejs/node/issues/45771.

You can already do the same thing like this:

if (process.env.NODE_ENV === 'test') {
  const test = require('node:test');
  // test things
}

cjihrig avatar Jul 28 '23 22:07 cjihrig

Code coverage may not easily reflect how much it is covered this way, the percentage will be misleading.

Lucas-Levandoski avatar Aug 01 '23 00:08 Lucas-Levandoski

Fair enough. You're welcome to open a PR and see how it is received. Just to warn you ahead of time, I think you'll need to add a new CLI flag to support this in the way you're asking for, and I can envision people pushing back on that for such a niche feature.

cjihrig avatar Aug 01 '23 01:08 cjihrig

There has been no activity on this feature request for 5 months and it is unlikely to be implemented. It will be 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.

github-actions[bot] avatar Jan 29 '24 01:01 github-actions[bot]

I solved this by using/abusing process.env.NODE_TEST_CONTEXT so the inline tests only run when --test is used. Is there a better condition to use? Out of the box, process.env.NODE_ENV is undefined when using --tests so you can't switch on it.

  • node ping.js: Does nothing (as expected)
  • node --test ping.js: Runs the tests (as expected)

ping.js

const test = require('node:test');
const assert = require('assert');

function ping() {
  return 'pong';
}

// Only run tests when using `--test` flag
if (process.env.NODE_TEST_CONTEXT) {
  test('ping', () => {
    assert.strictEqual(ping(), 'pong');
  });
}

module.exports = ping;

MadLittleMods avatar Feb 14 '24 11:02 MadLittleMods

I use if (process.env.NODE_ENV === "test" && require.main === module) {

"use strict";

function addTwo(a) {
  return internalAdder(a, 2);
}

if (process.env.NODE_ENV === "test" && require.main === module) {
  const assert = require("node:assert/strict");
  const { test } = require("node:test");

  test("addTwo()", () => {
    assert.equal(addTwo(4), 6);
  });
}

function internalAdder(a, b) {
  return a + b;
}

if (process.env.NODE_ENV === "test" && require.main === module) {
  const assert = require("node:assert/strict");
  const { test } = require("node:test");

  test("internal", () => {
    assert.equal(internalAdder(2, 2), 4);
  });

  test("float magic", () => {
    assert.equal(internalAdder(0.1, 0.2), 0.30000000000000004);
  });
}

module.exports = { addTwo };

And run the tests using test_runner.js

"use strict";

const fg = require("fast-glob").sync;
const { spec } = require("node:test/reporters");
const { run } = require("node:test");

process.env.NODE_ENV = "test";

const source = ["src/**/*.js", "test/**/*.test.js"];
const ignore = [];
const files = fg(source, { ignore })

files = fg(source, { ignore });
// console.log(files);

run({ files })
  .on("test:fail", () => {
    process.exitCode = 1;
  })
  .compose(new spec())
  .pipe(process.stdout);

pongo avatar Feb 14 '24 13:02 pongo

It doesn't seem like this has gained any traction (for the second time - #45771), so I'm going to close this. Anyone is free to open a PR implementing this though.

cjihrig avatar Mar 24 '24 14:03 cjihrig