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.
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.
- Define detailed test suites in the
-
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.
- 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 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');
});