Testing
foundation-testing is a comprehensive framework for Unit and End-to-End (E2E) testing, designed to validate components, user interactions, and workflows. Fully compatible with Genesis, React, and Angular frameworks, it ensures that your application can deliver a seamless and reliable user experience across diverse scenarios.
Unit testing
The foundation-testing package offers a comprehensive framework for implementing Unit Tests. Use it to validate the functionality of individual components, functions, and interactions, ensuring a seamless user experience.
Key tools and features
- 
Customizable Unit Test Suites: - Define detailed test suites in the tests/unitfolder to cover critical components and functions.
- Fully customizable to meet the needs of your application.
 
- Define detailed test suites in the 
- 
Sinon Integration: - Incorporate spies, stubs, and mocks into your tests using Sinon.JS, for powerful control over test behaviours.
 
- 
Lightweight Framework: - Minimal dependencies for a fast and efficient testing experience.
 
- 
High Performance: - Significantly faster than other test runners, ensuring quick feedback during development.
 
- 
Individually Executable Test Files: - Organize test suites into multiple files for clarity and manageability.
 
- 
Browser-Compatible: - Run and debug tests directly in a browser environment, using browser developer tools for enhanced debugging.
 
Getting started with unit testing
- Define test suites in the tests/unit folder, targeting critical components and functions.
- Use UVU for fast and lightweight Unit Testing.
- Use Sinon.JS if you need spies, stubs or mocks.
- Execute tests locally or on your CI pipeline, ensuring reliable and consistent outcomes.
E2E testing
The foundation-testing package also includes a powerful framework for End-to-End (E2E) Testing, ensuring seamless user experience by validating workflows and user interactions across diverse scenarios.
Key tools and features
- 
Customizable E2E Scenarios: - Define scenarios in the tests/e2efolder, focusing on user journeys and application workflows.
- Fully customizable to provide comprehensive test coverage.
 
- Define scenarios in the 
- 
Playwright Integration: - Automate browser actions and simulate real-world user interactions.
- Test across multiple browsers (Chrome, Firefox, WebKit) with parallel execution and context isolation.
- Capture screenshots, videos, and logs on test failures to aid debugging.
 
- 
Lighthouse Performance Testing: - Measure performance, accessibility, SEO, and quality metrics.
- Generate detailed reports for tracking performance and maintaining high standards.
 
- 
Automated CI Integration: - Integrate E2E tests into your CI pipeline to validate every codebase change before deployment, ensuring reliability and minimizing bugs.
 
Getting started with E2E testing
- Creating and running E2E Tests:
- Define test scenarios in the tests/e2e folder, targeting critical user journeys and application functionalities.
- Use Playwright for browser automation and Lighthouse for performance auditing within the same test suite.
- Execute tests via your preferred test runner or CI pipeline, ensuring reliable and consistent outcomes.
 
- Using Playwright:
- Install and configure Playwright as part of your project dependencies. Follow the set-up guide provided by the foundation-testing package.
- Write and run tests using the Playwright API to simulate real-world user interactions. These include clicking buttons, filling in forms, and navigating from page to page.
 
- Incorporating Lighthouse:
- Integrate Lighthouse into your E2E tests to run performance audits alongside functional tests.
- Use the insights from Lighthouse reports to identify and address performance issues, optimizing your application for speed and user experience.
 
- Advanced features:
- BDD Testing: Write behavior-driven tests using playwright-bdd, making it easier to define test scenarios and expectations in a human-readable format.
- Cross-Browser Testing: Use the Playwright cross-browser support to ensure your application works seamlessly across different browsers, including Chrome, Firefox, and WebKit.
- Visual Testing: Use the Playwrights screenshot and visual comparison features to validate the consistency of your UI across different devices and resolutions.
- Performance Budgets: Set performance thresholds using Lighthouse to maintain high performance standards, ensuring metrics like Time to Interactive (TTI) and First Contentful Paint (FCP) stay within acceptable limits.
 
- BDD Testing: Write behavior-driven tests using 
By combining Unit and E2E testing with the foundation-testing package, you can ensure that your web application delivers a reliable and optimized user experience.
Examples
Test organization
You can unit-test specific logic by adding a test file alongside the source file.
logic.ts
logic.test.ts
If your test spans more than one file or is more of an end-to-end test, you may wish to add your test to your package's /test directory instead. An example structure might be:
├── src
│   └── logic.ts
│   └── logic.test.ts
│   └── component.ts
│   └── component.test.ts
├── test
│   └── e2e
│       └── baseline.e2e.ts
│   └── unit
│       └── baseline.test.ts
├── package.json
└── playwright.config.ts
The contents of your package's playwright.config.ts may include:
export { configDefaults as default } from '@genesislcap/foundation-testing/e2e';
If you need to customise configuration, you can do it as follows:
import { configDefaults } from '@genesislcap/foundation-testing/e2e';
export default {
  ...configDefaults,
  // Any custom configuration here e.g. disabling the web server:
  webServer: undefined,
};
If you need to customize JSDOM, you can create a jsdom.setup.ts file in your package directory:
// custom code
export * from '@genesislcap/foundation-testing/jsdom';
Test scripts
The test-related scripts to add to your package's package.json file may include:
"test": "genx test",
"test:single": "genx test --match connect.test.ts",
"test:single:browser": "genx test --browser --match connect.test.ts",
"test:single:browser:raw-match": "genx test --browser --raw-match --match ./**/connect.test.ts",
"test:select": "genx test --match '(connect|reconnectStrategy|kv).test.ts'",
"test:select:browser": "genx test --browser --match '(connect|reconnectStrategy|kv).test.ts'",
"test:glob": "genx test --match reconnectStrategy*.test.ts",
"test:glob:browser": "genx test --browser --match reconnectStrategy*.test.ts",
"test:coverage": "genx test --coverage",
"test:coverage:browser": "genx test --coverage --browser",
"test:e2e": "genx test --e2e",
"test:e2e:debug": "genx test --e2e --debug",
"test:e2e:ui": "genx test --e2e --interactive",
"test:unit:browser": "genx test --browser",
"test:unit:browser:watch": "genx test --browser --watch",
"test:unit:watch": "genx test --watch",
"test:debug": "genx test --debug"
Testing logic
The logic.test.ts usually uses createLogicSuite, which tests function output given certain input
arguments. Based on user feedback, these arguments are now passed as an array by convention:
// logic.test.ts
import { createLogicSuite } from '@genesislcap/foundation-testing';
import { myFunction } from './logic';
const Suite = createLogicSuite('myFunction');
Suite('myFunction should provide expected results', ({ runCases }) => {
  runCases(myFunction, [
    [['1'], true],
    [[123], true],
    [['60%'], true],
    [['$60'], false],
    [['1.1'], false],
    [[''], false],
    [[true], false],
    [[null], false],
    [[undefined], false],
  ]);
});
Suite.run();
Testing components
The component.test.ts uses createComponentSuite (as does any test that directly or indirectly makes use of the DI). Apart from setting up and tearing down your element fixture with a wrapping design system and DI container, this utility also allows you to provide DI container mocks, which are required for certain testing flows to test web components effectively.
// component.test.ts
import { Connect } from '@genesislcap/foundation-comms';
import { ConnectMock } from '@genesislcap/foundation-comms/testing';
import { assert, createComponentSuite, Registration } from '@genesislcap/foundation-testing';
import { MyComponent } from './component';
/**
 * As we're using tag name in the Suite, we hold a reference to avoid tree shaking.
 */
MyComponent;
/**
 * Create mock
 */
const connectMock = new ConnectMock();
connectMock.nextMetadata = {
  FIELD: [
    {
      NAME: 'foo',
      TYPE: 'bar',
    },
  ],
};
/**
 * Resister mock instance
 */
const mocks = [Registration.instance(Connect, connectMock)];
const Suite = createComponentSuite<MyComponent>(
  'MyComponent',
  'my-component', // < or () => myComponent() if your component is composeable
  null,
  mocks,
);
Suite('Can be created in the DOM', async ({ element }) => {
  assert.ok(element);
});
Suite('Connect is mocked in the container', async ({ container }) => {
  const serviceMock = container.get(Connect);
  assert.instance(serviceMock, ConnectMock);
});
Suite('Attr changes update internals as expected', async ({ element }) => {
  element.setAttribute('resource-name', 'ALL_USERS');
  assert.match(element.optionsHash, /ALL_USERS/);
  element.setAttribute('order-by', 'USERNAME');
  assert.match(element.optionsHash, /USERNAME/);
});
Suite('Connect.getMetadata returns expected nextMetadata', async ({ container }) => {
  const serviceMock = container.get(Connect) as ConnectMock;
  /**
   * Assert base case
   */
  let serviceMeta = await serviceMock.getMetadata('someResource');
  assert.is(serviceMeta.FIELD[0].NAME, 'foo');
  /**
   * Apply next and assert
   */
  const metadata = {
    FIELD: [
      {
        NAME: 'hello',
        TYPE: 'world',
      },
    ],
  };
  serviceMock.nextMetadata = {
    ...metadata,
  };
  serviceMeta = await serviceMock.getMetadata('someResource');
  assert.equal(serviceMeta, {
    ...metadata,
  });
  // TODO: Trigger and cross check component reactions to underlying data changes
});
Suite.run();
Testing E2E
The baseline.e2e.ts uses playwright; test cases have access to the fixtures provided during set-up.
import { test, expect } from '@genesislcap/foundation-testing/e2e';
test('baseline test', async ({ page }) => {
    await page.goto('https://playwright.dev/');
    const name = await page.innerText('.navbar__title');
    expect(name).toBe('Playwright');
});
Unit testing resources and further documentation
E2E resources and further documentation
API documents
Find more details in our API documentation.