Skip to main content

Foundation 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 your application delivers a seamless and reliable user experience across diverse scenarios.

info

See complete set of API documentation here.

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/unit folder to cover critical components and functions.
    • Fully customizable to meet the needs of your application.
  • Sinon Integration:

    • Incorporate spies, stubs, and mocks into your tests using Sinon.JS, enabling powerful control over test behaviors.
  • 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, leveraging 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/e2e folder, focusing on user journeys and application workflows.
    • Fully customizable to provide comprehensive test coverage.
  • 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

  1. 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.
  2. 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.
  3. 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.
  4. 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.

By combining Unit and E2E testing with the foundation-testing package, you can ensure your web application delivers a reliable and optimized user experience.

Examples

Test Organisation

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 customise 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 is used to test 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 or any test that directly or indirectly makes use of the DI uses createComponentSuite. 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 effectively test web components.

// 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