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 🙏

© 2026 – Pkg Stats / Ryan Hefner

universal-common-test

v1.0.6

Published

A lightweight unit testing library based on MSTest.

Downloads

25

Readme

universal-common-test

A lightweight unit testing library based on MSTest that provides a simple and flexible testing framework for JavaScript applications.

Installation

npm install universal-common-test

Overview

This library provides a comprehensive testing framework with the following components:

  • TestEngine - Runs test classes and methods with full lifecycle support
  • TestResult - Encapsulates the result of a test execution
  • Assert - Provides assertion methods using JavaScript's loose equality semantics
  • StrictAssert - Provides assertion methods using strict equality checks
  • CollectionAssert - Specialized assertions for collections (arrays, sets, maps)
  • AssertFailedError - Exception thrown when assertions fail

Usage

Importing

// Import the test engine and result
import TestEngine from "universal-common-test/TestEngine.js";
import TestResult from "universal-common-test/TestResult.js";

// Import assertion utilities
import { Assert, StrictAssert, CollectionAssert, AssertFailedError } from "universal-common-test";

Basic Test Class

import { Assert } from "universal-common-test";

class CalculatorTests {
  // Instance field for test state
  calculator;
  
  // Called once before all tests in the class
  static classInitialize() {
    console.log("Setting up test class");
  }
  
  // Called before each test method
  testInitialize() {
    this.calculator = new Calculator();
  }
  
  // Test methods - any public method not in the lifecycle
  testAddition() {
    const result = this.calculator.add(2, 3);
    Assert.areEqual(5, result, "2 + 3 should equal 5");
  }
  
  testSubtraction() {
    const result = this.calculator.subtract(10, 4);
    Assert.areEqual(6, result);
  }
  
  // Called after each test method
  testCleanup() {
    this.calculator = null;
  }
  
  // Called once after all tests in the class
  static classCleanup() {
    console.log("Cleaning up test class");
  }
}

Running Tests

const engine = new TestEngine();

// Run all tests in a class
const results = await engine.runClassAsync(CalculatorTests);

// Run a specific test method
const singleResult = await engine.runMethodAsync(CalculatorTests, "testAddition");

// Run multiple test classes
const allResults = await engine.runAllAsync([
  CalculatorTests,
  StringUtilsTests,
  DatabaseTests
]);

Using Assertions

Assert (Loose Equality)

// Basic assertions
Assert.areEqual({a: 1, b: 2}, {a: 1, b: 2}); // Deep equality
Assert.areEquivalent("5", 5); // Uses == comparison
Assert.areSame(obj1, obj1); // Reference equality
Assert.isTrue(condition);
Assert.isFalse(condition);
Assert.isNull(value);
Assert.isNotNull(value);

// Type checking
Assert.isInstanceOf("hello", String);
Assert.isInstanceOf(42, "number");
Assert.isInstanceOf(new Date(), Date);

// Error assertions
Assert.throwsError(TypeError, () => {
  null.someMethod();
});

// Async error assertions
await Assert.throwsErrorAsync(ApiError, async () => {
  await api.unauthorizedCall();
});

StrictAssert (Strict Equality)

// Strict comparisons
StrictAssert.areEqual(5, 5); // Uses ===
StrictAssert.isTrue(true); // Must be exactly true
StrictAssert.isFalse(false); // Must be exactly false
StrictAssert.isNull(null); // Must be exactly null
StrictAssert.isUndefined(undefined); // Must be exactly undefined

// Type checking
StrictAssert.isTypeOf("hello", "string");
StrictAssert.isInstanceOf(new Map(), Map);

CollectionAssert

// Array/collection assertions
CollectionAssert.areEqual([1, 2, 3], [1, 2, 3]); // Same order
CollectionAssert.areEquivalent([3, 1, 2], [1, 2, 3]); // Any order
CollectionAssert.contains([1, 2, 3], 2);
CollectionAssert.doesNotContain([1, 2, 3], 4);

// Set operations
CollectionAssert.isSubsetOf([1, 2], [1, 2, 3, 4]);
CollectionAssert.isNotSubsetOf([1, 5], [1, 2, 3, 4]);

// Works with any iterable
CollectionAssert.areEqual(new Set([1, 2, 3]), [1, 2, 3]);
CollectionAssert.contains("hello", "e");

Async Test Methods

class AsyncTests {
  async testApiCall() {
    const response = await fetch("https://api.example.com/data");
    const data = await response.json();
    
    Assert.isNotNull(data);
    Assert.areEqual(200, response.status);
  }
  
  async testDatabaseQuery() {
    const users = await db.query("SELECT * FROM users");
    CollectionAssert.contains(users, expectedUser);
  }
}

Test Results

const engine = new TestEngine();
const results = await engine.runClassAsync(MyTests);

for (const result of results) {
  console.log(`${result.className}.${result.testName}:`);
  console.log(`  Passed: ${result.passed}`);
  console.log(`  Duration: ${result.duration}ms`);
  
  if (result.failed) {
    console.error(`  Error: ${result.error.message}`);
    console.error(`  Stack: ${result.error.stack}`);
  }
}

// Summary
const passed = results.filter(r => r.passed).length;
const failed = results.filter(r => r.failed).length;
console.log(`Total: ${results.length}, Passed: ${passed}, Failed: ${failed}`);

Custom Test Runner

class TestRunner {
  async runAllTests() {
    const testClasses = [
      UnitTests,
      IntegrationTests,
      PerformanceTests
    ];
    
    const engine = new TestEngine();
    const results = [];
    
    for (const testClass of testClasses) {
      console.log(`Running ${testClass.name}...`);
      const classResults = await engine.runClassAsync(testClass);
      results.push(...classResults);
      
      // Report progress
      const failedInClass = classResults.filter(r => r.failed);
      if (failedInClass.length > 0) {
        console.error(`  ${failedInClass.length} test(s) failed`);
      }
    }
    
    return results;
  }
}

Testing with Mock Objects

class ServiceTests {
  mockRepository;
  service;
  
  testInitialize() {
    // Create mock repository
    this.mockRepository = {
      findById: (id) => {
        if (id === 1) return { id: 1, name: "Test User" };
        return null;
      },
      save: (user) => {
        return { ...user, id: Date.now() };
      }
    };
    
    this.service = new UserService(this.mockRepository);
  }
  
  async testGetUser() {
    const user = await this.service.getUser(1);
    Assert.isNotNull(user);
    Assert.areEqual("Test User", user.name);
  }
  
  async testGetNonExistentUser() {
    await Assert.throwsErrorAsync(NotFoundError, async () => {
      await this.service.getUser(999);
    });
  }
}

Test Lifecycle

The TestEngine supports a comprehensive lifecycle pattern:

  1. Class Level:

    • static classInitialize() - Run once before all tests
    • static classCleanup() - Run once after all tests
  2. Test Level:

    • testInitialize() - Run before each test
    • testCleanup() - Run after each test

All lifecycle methods can be synchronous or asynchronous. If any initialization method fails, subsequent tests will fail with the initialization error.

Error Handling

class ErrorHandlingTests {
  testSynchronousError() {
    Assert.throwsError(TypeError, () => {
      const obj = null;
      obj.someMethod(); // Will throw TypeError
    });
  }
  
  async testAsynchronousError() {
    await Assert.throwsErrorAsync(NetworkError, async () => {
      await fetchWithTimeout("https://slow.example.com", 100);
    });
  }
  
  testCustomError() {
    Assert.throwsError(AssertFailedError, () => {
      Assert.areEqual(1, 2, "Numbers should be equal");
    });
  }
}

Advanced Usage

Parameterized Tests

class ParameterizedTests {
  testCases = [
    { input: [1, 2], expected: 3 },
    { input: [0, 0], expected: 0 },
    { input: [-1, 1], expected: 0 },
    { input: [10, -5], expected: 5 }
  ];
  
  testAdditionWithMultipleInputs() {
    const calculator = new Calculator();
    
    for (const testCase of this.testCases) {
      const [a, b] = testCase.input;
      const result = calculator.add(a, b);
      Assert.areEqual(
        testCase.expected, 
        result, 
        `Failed for inputs: ${a}, ${b}`
      );
    }
  }
}