npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@codejamboree/js-test

v2.5.3

Published

testing framework for javascript

Downloads

1,451

Readme

js-test

A simple test platform.

  • Run tests
    • Setup/Teardown all or individual tests
    • Run the same test with multiple test cases
    • Set timeout limits on all or individual tests
    • Randomize test order
    • Isolate or skip tests
  • Expectation helper
  • Mock functions
  • Spy & Fake standard output/error writes
  • Fake date creation
  • Fake performance now
  • Fake process hrtime
  • Fake random values
  • Fake http/https request

Running tests

The following will find all files in the src folder that end with .test.js, and run them.

import { run } from '@codejamboree/js-test';

run({
  folderPath: 'src',
  testFilePattern: /\.test\.js$/
}).then(() => {
  console.log('done');
})

Setup and tear down the entire test run, individual modules, or individual tests.

{
  beforeAll: () => {
    // I run before any test in the whole test-run begins
  },
  beforeSuite: () => {
    // I run before any of the tests in a module runs
  },
  beforeEach: () => {
    // I run before each test runs
  },
  afterEach: () => {
    // I run after each test completes
  },
  afterSuite: () => {
    // I run after all tests in a moudle completes
  },
  afterAll: () => {
    // I run after all tests in the entire project completes
  }
}

Other options are available, as well as the final results are returned.

import { run } from '@codejamboree/js-test';

run({
  folderPath: 'src',
  testFilePattern: /$([xf]_)?(.*)\.test\.js$/,
  testFileReplacement: '$2', // replacer for filename pattern
  timeoutMs: 300, // Limit time for each test to run
  failFast: true, // Stop all testing once a test fails
  randomOrder: true // Randomize the test order
}).then(results => {
  console.log('Failed', results.failed);
  console.log('Passed', results.passed);
  console.log('Skipped', results.skipped);
  console.log('Total', results.total);
  results.failures.forEach(failure => {
    console.group(failure.name);
    console.log('File', failure.filePath);
    console.log('Error', failure.error);
    console.groupEnd();
  });
})

Test Suite

A test suite is an individual file that exports functions as tests.

export const test1 = () => {
  let a = 1 + 2;
}
export const failingTest = () => {
  throw new Error('I have my reasons');
}
export const asyncTest = async () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("done");
    }, 100);
  });
}
asyncTest.timeoutMs = 200; // override timeout

Setup/Teardown

Special methods are ran before/after each test, or the entire set of tests in the file if present.

export const beforeAll = () => {}
export const beforeEach = () => {}
export const afterEach = () => {}
export const afterAll = () => {}
export const test = () => {}
test.before = () => {}
test.after = () => {}

Testing Lifecycle

  • runOptions.beforeAll()
  • loop through suites
    • runOptions.beforeSuite()
    • suite.beforeAll()
    • loop through each test / test case
      • runOptions.beforeEach()
      • suite.beforeEach()
      • test.before()
      • setTimeout
        • test()
      • test.after()
      • suite.afterEach()
    • suite.afterAll()
    • runOptions.afterSuite()
  • runOptions.afterAll()

Skipping Tests

Tests can be ignored by preceeding the name with an x_, or flagging the .skip property

export const test = () => {
  // I will run
}
export const x_stest = () => {
  // I will not run
}
export const flagged = () => {};
flagged.skip = true; // ensure flagged will not run

Focused Tests

Tests can be isolated by preceeding the name with an f_ or flagging the .focus property, causing all other tests to be skipped.

export const f_test = () => {
  // I will run
}
export const test = () => {
  // I will not run
}
export const flagged = () => {};
flagged.focus = true; // ensure flagged will run

Files and folders that contain tests may be renamed with f_ and x_ to focus and skip the entire file/directory of tests.

Test Cases

A test may have multiple test cases assigned to use repeated testing logic with different testing vectors. In this scenario, your test will have arguments, and an array of arguments to be tested will be assigned to the functions .testCases property.

export const test = (a, b, sum) => {
  if(a + b !== sum) {
    throw new Error(`${a + b} was not ${sum}`);
  }
}
test.testCases = [
  [37, 5, 42],
  [51, 75, 126],
  [22, 27, 49],
  [32, 56, 88],
  [83, 12, 95]
]

When tests are ran with random order, the order of test cases will also be random.

Expectation

An expectation helper assists in commong checks.

import { expect } from '@codejamboree/js-test';

export const test = () => {
  const target = "123";

  expect(target).is("123");
  expect(target).equals(123);
  expect(target).above("100");
  expect(target).below("150");
  expect(target).within("100", "150");
  expect(target).isFunction(); // error
  expect(target).lengthOf(3);
  expect(target).startsWith("1");
  expect(target).endsWith("3");
  expect(target).includes("2");
}
export const testInstance = () => {
  const target = new Date();
  expect(target).instanceOf(Date);
  expect(target).instanceOf('Date');
}
export const testErrors = () => {
  const message = 'This is an error';
  const customError = new Error(message);
  const target = () => {
    throw customError;
  };
  expect(target).toThrow();
  expect(target).toThrow(message);
  expect(target).toThrow(customError);
}

Additional details can help narrow down source of failure.

export const test = () => {
  const target = "123";
  expect(target, 'checking is').is("123");
  expect(target, 'comparing equal').equals(123);
  expect(target, 'third check').isFunction(false);
}

Negate method swaps expectations.

export const test = () => {
  const target = "123";

  expect(target).not().is(123);
  expect(target).not().equals("432");
  expect(target).not().above("150");
  expect(target).not().below("100");
  expect(target).not().within("200", "250");
  expect(target).not().isFunction();
  expect(target).not().lengthOf(42);
  expect(target).not().startsWith("3");
  expect(target).not().endsWith("1");
  expect(target).not().includes("9");
}

Mock Functions

Detect when functions were called, what they were called with, and control their response and behavior.

import { expect, mockFunction } from '@codejamboree/js-test';

export const test = () => {
  const target = mockFunction();

  target("apple", "banana");
  target("pizza");

  expect(target.called()).is(true);
  expect(target.callCount()).is(2);
  expect(target.callAt(0)).equals(["apple", "banana"]);
  expect(target.callArg(1, 0)).is("pizza");
}
export const testValue = () => {
  const target = mockFunction();
  target.returns("pickles");
  const value = target();
  expect(value).is("pickles");
}
export const testLogic = () => {
  const target = mockFunction(
    (food) => {
      return `I like ${food}`;
    }
  );
  const value = target("snacks");
  expect(value).is("I like snacks");
}

Standard Utility

Inspect write arguments to the standard output & standard error, and prevent them from being written.

import { expect, standardUtils } from '@codejamboree/js-test';

export const afterEach = () => {
  standardUtils.restore();
}

export const test = () => {
  console.log("You can see me");

  standardUtils.skipWrite();
  console.log("You can't see me");

  standardUtils.spy();
  console.log("Hidden 1");
  console.warn("Hidden 2");

  expect(standardUtils.writes(), 'spied').equals([
    'Hidden 1\n',
    "\u001b[33mHidden 2\u001b[39m\n"
  ]);
  expect(standardUtils.writeAt(0), 'start 1')
    .is('Hidden 1\n');
  expect(standardUtils.writeAt(1), 'start 2')
    .is("\u001b[33mHidden 2\u001b[39m\n");

  expect(standardUtils.typeAt(0)).is('standard');
  expect(standardUtils.typeAt(1)).is('error');

  standardUtils.unspy();
  console.log('Still hidden');
  expect(standardUtils.writeAt(-1), 'last')
    .is("\u001b[33mHidden 2\u001b[39m\n");

  standardUtils.allowWrite();
  console.log("You can see me again!");
  expect(standardUtils.writes(), 'all writes').equals([
    'Hidden 1\n',
    "\u001b[33mHidden 2\u001b[39m\n"
  ]);

  standardUtils.clearCaptured();
  expect(standardUtils.writes(), 'writes').equals([]);

}

Date Utility

Freeze time in place, or set it to a specific time.

import { expect, dateUtils } from '@codejamboree/js-test';

export const afterEach = () => {
  dateUtils.restore();
}

export const timeFrozen = async () => {
  dateUtils.freeze();
  return new Promise((resolve) => {
    const date = new Date();
    setTimeout(() => {
      expect(date.getTime()).is(new Date().getTime());
      resolve();
    }, 100);
  });
}

export const customTime = async () => {
  dateUtils.set(Date.UTC(1975, 4, 28, 3, 15, 1, 184));
  expect(new Date().toISOString()).is('1975-05-28T03:15:01.184Z');
}

export const timeRestored = () => {
  dateUtils.set(Date.UTC(1975, 4, 28, 3, 15, 1, 184));

  expect(new Date()).instanceOf('FakeDate');
  expect(new Date().toISOString()).is('1975-05-28T03:15:01.184Z');

  dateUtils.restore();
  
  expect(new Date()).not().instanceOf('FakeDate');
  expect(new Date().toISOString()).not().is('1975-05-28T03:15:01.184Z');
}

Performance Utility

Fake performance now.


export const test = () => {
  performanceUtils.freeze();
  const now1 = performance.now();
  const now2 = performance.now();
  expect(now1).is(now2);

  performanceUtils.set(123);
  expect(performance.now()).is(123);

  performanceUtils.restore();
  const now3 = performance.now();
  const now4 = performance.now();
  expect(now3).not().equals(now4);
}

Process Utility

Fake process high-resolution time

export const testFrozen = () => {
  standardUtils.spyAndHide();
  processUtils.freeze();
  console.time(label);
  console.timeEnd(label);
  expect(standardUtils.writeAt(-1)).equals(`${label}: 0ms\n`);
  processUtils.restore();
  standardUtils.restore();
}
export const testCustom = () => {
  standardUtils.spyAndHide();
  processUtils.set([1.000, 0]);
  console.time(label);
  processUtils.set([1.001, 0]);
  console.timeEnd(label);
  expect(standardUtils.writeAt(-1)).equals(`${label}: 1ms\n`);
  processUtils.restore();
  standardUtils.restore();
}

Chrono Utility

Freeze time-based methods and objects

export const test = () => {

  chronoUtils.freeze();

  const time1 = process.hrtime();
  const time2 = process.hrtime();
  expect(time1).equals(time2);

  const now1 = performance.now();
  const now2 = performance.now();
  expect(now1).equals(now2);

  const date1 = new Date();
  const date2 = new Date();
  expect(date1).equals(date2);

  chornoUtils.restore();
}

Math Random Utility

Ensure "random" numbers are deterministic.

// Psuedo Random Number Generator
mathRandomUtils.prng(8675309);
expect(Math.random()).is(0.8961716736183369);
expect(Math.random()).is(0.957318503389749);
expect(Math.random()).is(0.6520864715110913);

// Seed again
mathRandomUtils.prng(8675309);
expect(Math.random()).is(0.8961716736183369);

// Constant value
mathRandomUtils.setValue(0.3);
expect(Math.random()).is(0.3);
expect(Math.random()).is(0.3);

// List of values
mathRandomUtils.setValues([0.3, 0.7, 0.4]);
expect(Math.random()).is(0.3);
expect(Math.random()).is(0.7);
expect(Math.random()).is(0.4);
// Restarts at first value
expect(Math.random()).is(0.3);

// Custom function
let value = 0.3;
mathRandomUtils.setFunction(() => 0.1 + value);
expect(Math.random()).is(0.4);
value = 0.8;
expect(Math.random()).is(0.9);

// Cleanup
mathRandomUtils.restore();

Http Utility

Mimic requests and responses from the http/https request methods.

NOTE: Some methods/logic are missing from FakeClientRequest and FakeIncomingMessage.

export const afterEach = () => {
  httpUtils.restore();
}

export const status = async () => new Promise<void>((resolve, reject) => {
  httpUtils.setStatus(123, "The Status");
  const request = https.request("https://codejamboree.com");
  request.on('response', res => {
    expect(res.statusCode).is(123);
    expect(res.statusMessage).is('The Status');
    resolve();
  });
  request.on('error', reject);
  request.end();
});

export const chunks = async () => new Promise<void>((resolve) => {
  httpUtils.setChunks([
    'first',
    'second',
    'third'
  ]);
  /* As binary data
  httpUtils.setChunks([
    [ 0x66, 0x69, 0x72, 0x73, 0x74       ],
    [ 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64 ],
    [ 0x74, 0x68, 0x69, 0x72, 0x64       ]
  ]);
  */
  const chunksReceived: any[] = [];
  const request = https.request("https://codejamboree.com");
  request.on('response', res => {
    res.on('data', chunk => {
      chunksReceived.push(new TextDecoder().decode(chunk));
    });
    res.on('end', () => {
      expect(chunksReceived).equals([
        'first',
        'second',
        'third'
      ]);
      resolve();
    });
  });
  request.end();
});

export const callback = async () => new Promise<void>((resolve) => {
  httpUtils.mock();

  const callback = (res: http.IncomingMessage) => {
    res.on('end', () => {
      resolve();
    });
  };

  const request = https.request("https://codejamboree.com", callback);
  request.end();
});


export const postDataWithEncodedResponse = async () => new Promise<void>((resolve) => {
  httpUtils.mock();
  const responseData = JSON.stringify({ message: "Received!" });
  httpUtils.setResponseData(responseData);
  /* As binary data
  httpUtils.setResponseData([
    0x7b, 0x22, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
    0x65, 0x22, 0x3a, 0x20, 0x22, 0x52, 0x65, 0x63, 
    0x65, 0x69, 0x76, 0x65, 0x64, 0x21, 0x22, 0x7d
  ]);
  // 4 byte chunks
  httpUtils.setResponseData(responseData, 4);
  */

  const callback = (res: http.IncomingMessage) => {
    let receivedData: string = '';
    res.setEncoding('utf8');
    res.on('data', (chunk: string) => {
      receivedData += chunk;
    });
    res.on('end', () => {
      const parsed = JSON.parse(receivedData);
      expect(parsed.message).is('Received!');
      resolve();
    });
  };

  const url = new URL("https://codejamboree.com");

  const postData = JSON.stringify({ name: "Lewis Moten" });

  const options = {
    hostname: url.hostname,
    path: url.pathname,
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Content-Length': Buffer.byteLength(postData)
    }
  }
  const request = https.request(options, callback);
  request.write(postData);
  request.end();
});