Jasmine Testing Framework Documentation

repository·master·Indexed 20 days ago

https://github.com/jasmine/jasmine.github.io

Official documentation for the Jasmine testing framework, including API references, tutorials, and FAQs. This site provides detailed examples for module mocking across various environments, including ES module import maps in browsers, CommonJS and ESM in Node.js using TestDouble, and TypeScript integration using both CommonJS and Node 22+ native support.

Tokens
26.5K
Snippets
88
Records
117
Agent score
68%

What's inside Jasmine Documentation

  1. Testing TypeScript code with Jasmine

    master

    Jasmine can be used to test TypeScript code, but because the TypeScript ecosystem is fragmented, there is no single configuration that works for all projects. The setup depends on your TypeScript dialect and build tooling.

    Common strategies include:

    • Module Load-time Translation: Using a Node module loader (like @babel/register or tsx) to translate TypeScript to JavaScript as modules are loaded. This is fast but lacks type checking.
    • Node Native Support: Using Node's built-in ability to run TypeScript (available in newer Node versions). This is the least setup but has specific syntax and import requirements.
    • Pre-compilation: Compiling TypeScript to JavaScript files on disk using tsc before running Jasmine. This provides type checking but has a slower edit-compile-run cycle.
    • Web Test Runner: Using Web Test Runner with the esbuild plugin and web-test-runner-jasmine.
  2. Understand Module Mocking in Jasmine

    master

    Module mocking is a testing technique where a test replaces all or parts of a module that is imported by another module, without requiring cooperation from either module.

    When to use it

    • Use it when: You need to test legacy code that is tightly coupled to its dependencies and cannot easily be refactored for dependency injection.
    • Avoid it when: You can use Dependency Injection, which is generally considered a better architectural choice.

    Trade-offs

    • Pros: Allows testing of hard-wired dependencies and legacy code without refactoring.
    • Cons:
      • Masks Coupling: It prevents you from receiving feedback about excessive coupling in your architecture.
      • Global State Mutation: It alters global state, which can lead to flaky tests if mocks are not reset between tests.
      • Non-standard Behavior: It mutates variables in other files without their knowledge, which goes against standard JavaScript behavior.
      • Fragility: Many techniques rely on unstable APIs or private implementation details of Node.js, bundlers, or transpilers, making them prone to breaking during updates.
  3. Best practices for custom reporters

    master

    When implementing a custom reporter, ensure it handles all possible failure modes.

    Key considerations:

    • Failure Reporting: Ensure you handle failures at the global level (jasmineDone), suite level (suiteDone), and spec level (specDone).
    • Exclusions: In Jasmine 3.0+, specs that are not run (e.g., due to being non-fdescribed) are referred to as excluded. Your reporter should correctly reflect this state.
    • Testing your reporter: You can validate your reporter against existing Jasmine suites like jasmine_failure_types.js (to check failure reporting) and jasmine_exclusions.js (to check exclusion reporting).
  4. Configure reporters for parallel mode

    master

    Parallel mode changes how events are delivered and processed. To ensure a reporter works in parallel mode, it must meet the following criteria:

    Compatibility Declaration

    Jasmine assumes reporters are incompatible with parallel mode unless they explicitly declare compatibility by exposing a reporterCapabilities property:

    // Example declaration
    reporter.reporterCapabilities = { parallel: true };

    Key Behavioral Changes

    • Interleaved Events: Events for unrelated specs and suites can interleave. Do not assume that a specDone event belongs to the most recent specStarted event. Instead, use the parentSuiteId property (available in Jasmine 4.6+) to track hierarchy.
    • Concurrent Dispatch: Jasmine does not wait for asynchronous reporter functions to complete in parallel mode. Reporters must be able to handle events concurrently or queue them internally.
    • Event Guarantees: Jasmine only guarantees that:
      • jasmineStarted is reported before all other events.
      • jasmineDone is reported after all other events.
      • specStarted precedes its corresponding specDone.
      • suiteStarted precedes its corresponding suiteDone.
      • All children events occur between a suite's suiteStarted and suiteDone.

    Unavailable API/Fields

    • Env#topSuite is unavailable.
    • jasmineStarted event lacks totalSpecsDefined and order fields.
    • JasmineDone event lacks the order field.
  5. Handle Vite-specific extensions and non-standard imports

    master

    TypeScript projects often use non-standard extensions like CSS imports, JSON imports, or Vite-specific features (import.meta, process.env substitution) that are not valid JavaScript. To test these, you must transform them into valid JavaScript.

    Strategies:

    • Pre-compilation: Consult your TypeScript build tool's documentation.
    • Module Loaders: Use additional loaders to handle non-standard features. For example, the ignore-styles CommonJS loader can turn CSS imports into no-ops.
    • Dependency Injection: For features like process.env or import.meta, the recommended best practice is to use dependency injection to decouple your code from these environment-specific features.
  6. How custom equality testers work

    master

    Jasmine allows you to override the default equality logic by defining a custom equality tester.

    A custom equality tester is a function that accepts two arguments (first and second).

    Behavioral Rules:

    • Return true or false: If the tester can handle the comparison, it must return a boolean. If it returns false, Jasmine will stop there and consider the objects unequal; it will not fall back to default equality testing.
    • Return undefined: If the tester does not know how to compare the two specific items, it must return undefined. This signals Jasmine to move on to the next registered tester or fall back to its default equality logic.

    Custom testers are checked before the default equality tests and work recursively during nested equality checks (e.g., inside arrays or objects).

    function myCustomTester(first, second) {
      if (typeof first === 'string' && typeof second === 'string') {
        return first[0] === second[1];
      }
      // Return undefined if this tester doesn't handle the types
    }
  7. Configure Jasmine for ES Modules or CommonJS

    master

    Jasmine uses dynamic imports, making it compatible with both ES modules (files ending in .mjs or packages with "type": "module") and CommonJS.

    If you need to force Jasmine to load scripts using require (for specific CommonJS compatibility), add "jsLoader": "require" to your configuration file. Note that .mjs files will always be loaded via dynamic import regardless of this setting.

  8. Evaluate if you should share test behaviors

    master

    Before implementing loops or helper functions to share behaviors, ask the following questions to ensure the trade-off is worth it:

    1. Intent vs. Convenience: Is it important that all suites behave identically, or are you just trying to save typing? Are you communicating a requirement that these things must behave the same?
    2. Maintainability: If the behavior were duplicated instead of shared, how much harder would it be to maintain?
    3. Understandability: How easy is it for a new reader to understand the resulting test code?
    4. Debuggability: How easy is it to debug failures? (Note: Loops and helpers can make searching for failed spec names harder and can result in less useful stack traces, especially in async specs).
  9. Define custom object formatters for matcher failure messages

    master

    You can improve the readability of Jasmine matcher failure messages by defining custom object formatters. A custom object formatter is a function that takes a value and returns a string representation if it knows how to describe that object, or undefined if it does not recognize the object.

    When a test fails, Jasmine will use these formatters to represent complex objects in the error output, making it easier to understand why a comparison failed.

    function formatCell(val) {
        if (val.hasOwnProperty('entry') && val.hasOwnProperty('correctValue')) {
            const entries = val.entry.pencil
                ? 'pencil entries: ' + val.entry.numbers.join(',')
                : 'entry: ' + val.entry.number;
    
            return '<cell ' + entries + ', correct: ' + val.correctValue + '>';
        }
        // Return undefined if the object is not a 'cell'
    }
  10. How Jasmine manages asynchronous work

    master

    Jasmine requires explicit notification to know when asynchronous work is finished. If no asynchronous mechanism is detected, Jasmine assumes the work is synchronous and moves to the next item in the queue as soon as the function returns.

    Jasmine supports three primary mechanisms for managing async work, which can be used in beforeEach, afterEach, beforeAll, afterAll, and it blocks:

    1. async/await: The most convenient method. Jasmine waits for the implicitly returned promise to resolve or reject.
    2. Promises: Explicitly returning a promise (or any object with a .then method) tells Jasmine to wait for resolution.
    3. Callbacks: Passing a done function to the test function. Jasmine waits until done is invoked.
  11. Configure ES module support and Import Maps

    master

    When using ES modules, files ending in .mjs are loaded as modules. If your source files are ES modules, your spec files must also be ES modules.

    To allow relative imports between specs and source files, set specDir to a high-level directory containing both, and set srcFiles to []. You can automate this setup with npx jasmine-browser-runner init --esm.

    You can also define importMap in your configuration to map module names to specific paths or URLs.

    export default {
       // ...
       "importMap": {
         "moduleRootDir": "node_modules", 
         "imports": {
           "some-lib":"some-lib/dist/index.mjs",
           "some-lib/": "some-lib/dist/",
           "some-cdn-lib": "https://example.com/some-cdn-lib"
          }
       }
    }
  12. Initialize Jasmine in a Rails or non-Rails project

    master

    Use the following commands to set up your environment:

    For Rails projects: Use the Rails generator to install a default jasmine.yml and a sample jasmine_helper.rb.

    rails g jasmine:install

    To install example specs and implementations:

    rails g jasmine:examples

    For non-Rails projects: Use the jasmine command line tool. This will install the necessary files and modify your Rakefile to load jasmine tasks.

    jasmine init

    To install example specs:

    jasmine examples
    rails g jasmine:install
    # or
    jasmine init