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 🙏

© 2025 – Pkg Stats / Ryan Hefner

bdd-lazy-var-next

v0.0.5

Published

Provides helpers for testing frameworks such as mocha/jasmine/jest/vitest/bun which allows to define lazy variables and subjects

Readme

BDD + lazy variable definition (aka rspec)

Note: This is a fork of the original bdd-lazy-var with added support for Vitest and Bun test frameworks.

NPM version CI GitHub

Provides helpers for testing frameworks such as bun:test, vitest, jest, mocha and jasmine which allows to define lazy variables and subjects.

⚠️ [!WARNING] > Breaking Changes from bdd-lazy-var

If you are migrating from the original library, please note the following critical changes:

  1. No Auto-Detection: You must import from the specific framework entry point (e.g., bdd-lazy-var-next/bun, bdd-lazy-var-next/jest). The generic bdd-lazy-var import is not supported.
  2. Explicit Imports Only: Global variables (def, get, subject) are no longer supported. You must explicit import them.
  3. Native ESM: This library is published as native ESM. Ensure your environment supports ESM.
  4. Removed its Shortcut: The its shortcut and is.expected helper have been removed to simplify the API and reduce maintenance. Use standard it blocks with assertions instead.

Installation

npm install bdd-lazy-var-next --save-dev
# or
bun add -d bdd-lazy-var-next

Setup & Configuration

Important: Unlike the original bdd-lazy-var, this library requires you to import the specific entry point for your testing framework.

Bun

import { get, def, subject } from "bdd-lazy-var-next/bun";

describe("My Bun Test", () => {
  def("value", () => 1);
  // ...
});

Bun Test Isolation

Bun runs all test files in a shared global context. This means global variable definitions (e.g., def('foo')) can collide across files, causing errors like "Cannot define variable twice".

Solutions:

  • Define variables inside describe only
  • Use unique variable names or suite names in each test file.

Example:

Bun Consumer Example

Vitest

Note: You must set globals: true in your Vitest configuration for bdd-lazy-var-next to work correctly. This is because the library relies on global lifecycle hooks provided by Vitest (like beforeAll, afterAll).

// vitest.config.ts
export default defineConfig({
  test: {
    globals: true,
  },
});
// test/example.test.ts
import { get, def } from "bdd-lazy-var-next/vitest";

describe("My Vitest Test", () => {
  // ...
});

Example:

Vitest Consumer Example

Jest

🚨 CAUTION: Do yourself a favor and migrate to Vitest or Bun test. Jest is slow, has poor ESM support, and is no longer actively innovated. Don't use Jest.

Important: You must use the global describe, it, test, and expect variables provided by Jest. Importing them from @jest/globals is not supported and will cause "Cannot define variable twice" errors because the library cannot intercept those imports to add its tracking logic.

import { get, def } from "bdd-lazy-var-next/jest";

Example:

Jest Consumer Example

Mocha

🚨 CAUTION: Do yourself a favor and migrate to Vitest or Bun test. Mocha is outdated and lacks modern features like native ESM support, built-in TypeScript, and parallel testing. Don't use Mocha.

import { get, def } from "bdd-lazy-var-next/mocha";

Jasmine

🚨 CAUTION: Do yourself a favor and migrate to Vitest or Bun test. Jasmine is legacy technology with poor ESM support and minimal modern tooling integration. Don't use Jasmine.

import { get, def } from "bdd-lazy-var-next/jasmine";

Usage Guide

Basic Usage

The core concept is defining variables that are lazily evaluated and automatically cleaned up.

import { get, def } from "bdd-lazy-var-next/bun"; // or /vitest, /jest, /mocha, /jasmine

describe("Suite", () => {
  // Define a variable 'name'
  def("name", () => `John Doe ${Math.random()}`);

  it("defines `name` variable", () => {
    // Access it using get()
    expect(get("name")).to.exist;
  });

  it("does not use name, so it is not created", () => {
    expect(1).to.equal(1);
  });
});

Lazy Evaluation

Variables are instantiated only when referenced. That means if you don't use variable inside your test it won't be evaluated, making your tests run faster.

Composition

Due to laziness we are able to compose variables. This allows to define more general variables at the top level and more specific at the bottom:

describe('User', function() {
  subject('user', () => new User(get('props')))

  describe('when user is "admin"', function() {
    def('props', () => ({ role: 'admin' }))

    it('can update articles', function() {
      // user is created with property role equal "admin"
      expect(get('user')).to....
    })
  })

  describe('when user is "member"', function() {
    def('props', () => ({ role: 'member' }))

    it('cannot update articles', function() {
      // user is created with property role equal "member"
      expect(get('user')).to....
    })
  })
})

Named Subjects

You can give your subject a name to reference it explicitly, or use the default subject alias.

describe("Array", () => {
  subject("collection", () => [1, 2, 3]);

  it("has 3 elements by default", () => {
    expect(get("subject")).to.equal(get("collection"));
    expect(get("collection")).to.have.length(3);
  });
});

Advanced Features

Shared Examples

Very often you may find that some behavior repeats (e.g., when you implement Adapter pattern), and you would like to reuse tests for a different class or object.

  • sharedExamplesFor - defines a set of reusable tests.
  • includeExamplesFor - runs previously defined examples in current context (i.e., in current describe).
  • itBehavesLike - runs defined examples in nested context (i.e., in nested describe).

WARNING: files containing shared examples must be loaded before the files that use them.

sharedExamplesFor("a collection", (size) => {
  it("has correct size", () => {
    expect(get("subject").size).to.equal(size);
  });
});

describe("Set", () => {
  subject(() => new Set([1, 2, 7]));
  itBehavesLike("a collection", 3);
});

describe("Map", () => {
  subject(() => new Map([[2, 1]]));
  itBehavesLike("a collection", 1);
});

TypeScript Support

bdd-lazy-var-next includes full TypeScript support with generic types for type-safe variable definitions and access.

Configuration

The library uses package.json exports field to provide framework-specific entry points (./bun, ./jest, ./vitest, etc.). Your TypeScript configuration needs to support this.

For Node.js / Backend Projects (Recommended)

Use NodeNext for the most complete and reliable support:

{
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "isolatedModules": true
    // ... other options
  }
  // ... other options
}

Why NodeNext?

  • Fully supports package.json exports field with conditional exports
  • Correctly resolves the types field for subpath imports like bdd-lazy-var-next/jest
  • Best option for Node.js, Bun, Jest, Mocha testing environments

For Frontend / Bundler Projects

If you're using Vite, Webpack, or other bundlers (e.g., for Vitest in a frontend app), you can use:

{
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "bundler",
    "isolatedModules": true
    // ... other options
  }
  // ... other options
}

Note: bundler moduleResolution should work for most cases, but if you encounter type resolution issues with subpath imports, switch to NodeNext.

Basic Usage

When using explicit imports, TypeScript loads corresponding declarations automatically:

import { get, def } from "bdd-lazy-var-next/bun";

Type-Safe Variables

All functions (def, get, subject) support TypeScript generics for type safety.

Method 1: Explicit Type Parameters (Recommended)

Specify the type when calling get() for full type safety:

import { def, get } from "bdd-lazy-var-next/bun";

// Define your types
interface User {
  name: string;
  age: number;
  email: string;
}

// Define variables (types are optional here)
def("userName", () => "John Doe");
def("user", () => ({
  name: "John Doe",
  age: 30,
  email: "[email protected]",
}));
def("scores", () => [95, 87, 92, 88]);

// Get variables with explicit type parameters
const userName = get<string>("userName");
console.log(userName.toUpperCase()); // Type-safe string methods ✓

const user = get<User>("user");
console.log(user.email); // Type-safe property access ✓

const scores = get<number[]>("scores");
const average = scores.reduce((a, b) => a + b, 0) / scores.length; // ✓

Method 2: Variable Type Annotations

Let TypeScript infer from your variable declaration:

// TypeScript infers the type from the annotation
const userName: string = get("userName");
console.log(userName.toUpperCase()); // Works!

const user: User = get("user");
console.log(user.email); // Works!

const scores: number[] = get("scores");
console.log(scores.length); // Works!

Typing def() (Optional)

You can also add types to def() for consistency:

// Type the definition function
def<string>("userName", () => "John Doe");
def<User>("user", () => ({
  name: "John Doe",
  age: 30,
  email: "[email protected]",
}));
def<number[]>("scores", () => [95, 87, 92, 88]);

Type-Safe subject()

Define and access subjects with types:

import { subject } from "bdd-lazy-var-next/bun";

describe("User", () => {
  // Named subject with type
  subject<User>("currentUser", () => ({
    name: "Jane Smith",
    age: 25,
    email: "[email protected]",
  }));

  it("has correct properties", () => {
    // Access with type safety
    const user = subject<User>();
    expect(user.name).toBe("Jane Smith");
  });
});

describe("Array operations", () => {
  // Anonymous subject with type
  subject<number[]>(() => [1, 2, 3, 4, 5]);

  it("calculates sum", () => {
    const numbers = subject<number[]>();
    const sum = numbers.reduce((a, b) => a + b, 0);
    expect(sum).toBe(15);
  });
});

Advanced Type Usage

You can use any TypeScript type, including generics and unions:

// Generic types
def<Record<string, number>>("scores", () => ({
  math: 95,
  science: 87,
  english: 92,
}));

// Union types
def<"active" | "inactive" | "pending">("status", () => "active");

// Function types
def<(x: number) => number>("double", () => (x) => x * 2);

// Promise types
def<Promise<User>>("asyncUser", async () => {
  return await fetchUser();
});

Type Safety Benefits

  1. Compile-time type checking: Catch type errors before runtime
  2. IntelliSense support: Get autocomplete and inline documentation
  3. Refactoring safety: TypeScript will flag issues when types change
  4. Self-documenting code: Types serve as inline documentation

Complete Example with Type Safety

import { describe, it, expect } from "bun:test";
import { def, get, subject } from "bdd-lazy-var-next/bun";

interface Product {
  id: number;
  name: string;
  price: number;
}

describe("Shopping Cart", () => {
  def("products", () => [
    { id: 1, name: "Laptop", price: 999 },
    { id: 2, name: "Mouse", price: 25 },
  ]);

  def("quantities", () => [1, 2]);

  subject("totalPrice", () => {
    // Use explicit type parameters for type safety
    const products = get<Product[]>("products");
    const quantities = get<number[]>("quantities");

    return products.reduce((total, product, index) => {
      return total + product.price * quantities[index];
    }, 0);
  });

  it("calculates total price correctly", () => {
    const total = subject<number>();
    expect(total).toBe(1049); // 999*1 + 25*2
  });

  it("has type-safe property access", () => {
    const products = get<Product[]>("products");

    // TypeScript knows products is Product[]
    expect(products[0].name).toBe("Laptop"); // ✓ Type-safe!
    expect(products[0].price).toBe(999); // ✓ Autocomplete works!
    expect(products.length).toBe(2);
  });

  it("alternative: using variable type annotations", () => {
    // You can also use type annotations instead of explicit parameters
    const products: Product[] = get("products");
    const quantities: number[] = get("quantities");

    expect(products.length).toBe(2);
    expect(quantities.length).toBe(2);
  });
});

React Testing Library Example

bdd-lazy-var-next works perfectly with React Testing Library for component testing:

import { describe, it, expect, mock } from "bun:test";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { def, get, subject } from "bdd-lazy-var-next/bun";
import { UserProfile, type User } from "./UserProfile";

describe("UserProfile Component", () => {
  // Define user with type safety
  def("user", () => ({
    id: get<number>("userId"),
    name: get<string>("userName"),
    email: get<string>("userEmail"),
    role: get<"admin" | "user">("userRole"),
  }));

  def("userId", () => 1);
  def("userName", () => "John Doe");
  def("userEmail", () => "[email protected]");
  def("userRole", () => "user" as const);

  def("onEdit", () => mock(() => {}));
  def("onDelete", () => mock(() => {}));

  // Subject: render the component
  subject("profile", () =>
    render(
      <UserProfile
        user={get<User>("user")}
        onEdit={get("onEdit")}
        onDelete={get("onDelete")}
      />
    )
  );

  it("renders user information", () => {
    subject();
    expect(screen.getByTestId("user-name")).toHaveTextContent("John Doe");
    expect(screen.getByTestId("user-email")).toHaveTextContent(
      "[email protected]"
    );
  });

  it("calls onEdit when clicked", async () => {
    subject();
    await userEvent.click(screen.getByTestId("edit-button"));
    expect(get("onEdit")).toHaveBeenCalledTimes(1);
  });

  describe("admin user", () => {
    def("userName", () => "Admin User");
    def("userRole", () => "admin" as const);

    it("displays admin role", () => {
      subject();
      expect(screen.getByTestId("user-role")).toHaveTextContent("Role: admin");
    });
  });
});
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { def, get, subject } from "bdd-lazy-var-next/vitest";
import { Counter } from "./Counter";

describe("Counter Component", () => {
  def("counterProps", () => ({
    initialCount: get<number>("initialCount"),
    label: get<string>("label"),
  }));

  def("initialCount", () => 0);
  def("label", () => "Count");

  subject("counter", () => render(<Counter {...get("counterProps")} />));

  it("renders with default count", () => {
    subject();
    expect(screen.getByTestId("count-label")).toHaveTextContent("Count: 0");
  });

  it("increments the counter", async () => {
    subject();
    await userEvent.click(screen.getByTestId("increment"));
    expect(screen.getByTestId("count-label")).toHaveTextContent("Count: 1");
  });

  describe("with custom initial count", () => {
    def("initialCount", () => 10);

    it("starts at the custom count", () => {
      subject();
      expect(screen.getByTestId("count-label")).toHaveTextContent("Count: 10");
    });
  });
});

Key Benefits for React Testing:

  1. DRY Component Setup: Define props once, reuse in nested contexts
  2. Easy Overrides: Override specific props in nested describe blocks
  3. Type Safety: Full TypeScript support with React components
  4. Clean Tests: Focus on behavior, not setup boilerplate
  5. Lazy Rendering: Components only render when subject() is called

Important: Prevent Memory Leaks

When using React Testing Library with bdd-lazy-var-next, you must add cleanup to prevent memory leaks:

For Bun:

// setup.ts
import { cleanup } from "@testing-library/react";
import { afterEach } from "bun:test";

afterEach(() => {
  cleanup();
});

For Vitest:

// setup.ts
import { cleanup } from "@testing-library/react";
import { afterEach } from "vitest";

afterEach(() => {
  cleanup();
});

Why this matters:

Without cleanup, rendered components accumulate in memory across tests, causing:

  • 🐌 Slow test execution (especially noticeable in Vitest)
  • 💾 Memory leaks from accumulated DOM nodes and React instances
  • Test interference from leftover state

The memory leak is particularly bad when using subject() with render() because each test renders a component that stays mounted unless explicitly cleaned up.

Bun Advanced Usage & Troubleshooting

Local Development & Linking

When testing changes in a local consumer project (e.g., examples/bun-consumer), you may want to link the package locally:

// package.json
"dependencies": {
  "bdd-lazy-var-next": "file:../../"
}

Note: Linking with file:../../ will copy the entire repo, including node_modules, which can be slow. For faster linking, use a minimal package or run npm pack/bun pack in the main repo and link the resulting .tgz file:

cd /Users/sujeetkc1/Desktop/bdd-lazy-var-next
bun run build
bun pack # or npm pack
# Then in consumer project:
bun add ../bdd-lazy-var-next/bdd-lazy-var-next-x.y.z.tgz

Preload Setup for Bun

To register globals for all tests, use Bun's preload feature in bunfig.toml:

[test]
preload = ["./setup.ts"]
// setup.ts
import "bdd-lazy-var-next/bun";

Troubleshooting

  • Double Initialization Error: If you see errors about variables being defined twice, ensure you are not importing the library globally in multiple places, and use unique variable names per test file.
  • Local Linking Slow: Use a packed .tgz file for local development to avoid copying the entire repo.
  • TypeScript Types: Ensure your tsconfig.json includes the correct type paths for Bun and the library.

Motivation: Why the new way rocks

No more global leaks

Because lazy vars are cleared after each test, we didn't have to worry about test pollution anymore. This helped ensure isolation between our tests, making them a lot more reliable.

Clear meaning

Every time I see a get('<variable>') reference in my tests, I know where it's defined. That, coupled with removing exhaustive var declarations in describe blocks, have made even my largest tests clear and understandable.

The old way (for comparison)

describe("Suite", function () {
  var name;

  beforeEach(function () {
    name = getName();
  });

  afterEach(function () {
    name = null;
  });

  it("uses name variable", function () {
    expect(name).to.exist;
  });
});

This pattern becomes difficult as tests grow, leading to "variable soup" and potential leaks.

Want to help?

Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on guidelines for contributing

License

MIT License