Mocking
In Archibald, all HTTP requests sent to the Backend for Frontend (BFF) can be fully mocked. This enables us to develop features independently of the backend team.
Without mocking, client requests pass through the BFF to a Hybris endpoint, which returns the actual data. However, when mocking is enabled, the requests are routed to an internal BFF endpoint that provides mocked data instead.
Data fetching with Hybris BE
Data fetching with mocking
Mocking a CMS component
After you create a new CMS component you can test it by mocking it in a JSON file that represents a mock page response. Ensure you add the mock data in the correct language-specific JSON file (de or en), depending on the page you're testing.
shop/server/api/modules/mock/responses/v2/de/cms/home-page.json
{
"name": "Frontpage",
"contentSlots": [
{
"slotId": "ContentSlot",
"position": "Content",
"components": [
//...
{
"typeCode": "CmsInvoicesComponent",
"columns": "12"
},
],
}
}
Configuring Route Mocking
The configuration of which routes are mocked is handled through the environment settings. You can control mocking globally by setting a boolean flag, which toggles mocking on or off for all routes.
For more granular control, an array of strings or regular expressions can be provided in the configuration. This array is used to match routes, determining whether a specific route should be mocked or not. By defining specific patterns, you can selectively mock certain API endpoints while allowing others to interact with the real backend.
hybris: {
api: {
mocked: boolean | RegExp | (RegExp | string)[];
}
}
Here’s an example configuration that mocks all routes defined in shop/server/routes.ts that match either cms/pages or messages. This setup is the default configuration for the local-stage environment, ensuring that these specific routes return mocked data during development.
hybris: {
api: {
mocked: ['cms/pages', 'messages'];
}
}
Mocking a route example
After creating and mocking a CMS component, the next step is to fetch data for display. Since the backend endpoint for this data isn't ready yet, we'll create a mock endpoint that returns sample data.
Client
-
Define a new TypeScript interface for the structure of the data that will be fetched.
shop/client/features/account/interfaces/invoice.ts
export interface Invoice {id: string;name: string;totalPrice: number;} -
Define a new entity based on the interface.
shop/client/features/account/actions/invoice.ts
import { createRequest } from 'shop/client/api';import { Invoice } from 'shop/client/features/account/interfaces/invoice';export function actionGetUserInvoices(userId: string = 'current') {return createRequest<Invoice[]>({url: `users/${userId}/invoices`});} -
Build a hook that calls the necessary action to fetch the data and returns it for use within the component.
shop/client/features/account/hooks/useInvoices.ts
import { useFetch } from '@archibald/client';import { actionGetUserInvoices } from 'shop/client/features/account/actions/invoice';export function useInvoices(userId?: string) {return useFetch(['account', 'invoices', userId], () => actionGetUserInvoices(userId));} -
Use the custom hook inside your component to retrieve and display the mocked data.
import { useInvoices } from 'shop/client/features/account/hooks/useInvoices';import { useUserId } from 'shop/client/hooks/user';export function InvoicesComponent() {const userId = useUserId();const { data: invoices } = useInvoices(userId);return (<div>My Invoices:<ul>{invoices?.map((invoice) => (<li key={invoice.id}>{invoice.name}: {invoice.totalPrice}</li>))}</ul></div>);}
Server
-
Define a set of mock data that represents the expected structure and content of the real backend response.
shop/server/api/modules/mock/entities/invoices.ts
export const MockDefaultInvoices = [{'id': '1','name': 'Invoice 1','totalPrice': 212.15},{'id': '2','name': 'Invoice 2','totalPrice': 31.21},{'id': '3','name': 'Invoice 3','totalPrice': 871.45},{'id': '4','name': 'Invoice 4','totalPrice': 120.15}]; -
Create a mock service that holds the mock data. Use the PersistenceService to store and retrieve the data.
shop/server/api/modules/mock/services/invoice.ts
import { BaseService, Inject, PersistenceService } from '@archibald/server';import { MockDefaultInvoices } from 'shop/server/api/modules/mock/entities/invoices';import { Invoice } from 'shop/client/features/account/interfaces/invoice';export class MockInvoiceService extends BaseService {@Inject()private readonly persistenceService: PersistenceService<Invoice[]>;constructor() {super();this.persistenceService.set('invoices', MockDefaultInvoices, { try: false });}public async getUserInvoices(id?: string) {if (!id) {return undefined;}return await this.persistenceService.get('invoices');}}export default MockInvoiceService; -
Develop a mock controller that handles the incoming requests, interacts with the mock service, and returns the mock data as a response.
shop/server/api/modules/mock/controllers/invoice.ts
import { HttpError, wait } from '@archibald/core';import { Logger } from '@archibald/log';import { BaseController } from '@archibald/server';import Boom from '@hapi/boom';import { Request, ResponseToolkit } from '@hapi/hapi';import MockInvoiceService from 'shop/server/api/modules/mock/services/invoice';export class MockInvoiceController extends BaseController {protected override readonly name: string = 'MockInvoiceController';private readonly invoiceService: MockInvoiceService;constructor() {super();this.logControllerRegister(this.name);this.invoiceService = new MockInvoiceService();}protected handleError(exception: unknown, h: ResponseToolkit, fallbackErrorText = 'Error on processing invoice') {Logger.error(exception);if (exception instanceof HttpError) {throw Boom.boomify(exception, exception);}throw Boom.badRequest(fallbackErrorText);}public async getUserInvoices(request: Request, h: ResponseToolkit) {try {this.logControllerEntry(request, 'get', this.name);const invoiceResponse = await this.invoiceService.getUserInvoices(request.params.userId);await wait(200);return h.response(invoiceResponse);} catch (exception) {throw this.handleError(exception, h, 'Error while getting user invoices');}}} -
Define a new internal route for the mock endpoint, and ensure it invokes the mock controller to return the mock data.
shop/server/api/modules/mock/index.ts
export class MockModule extends BaseModule {//...public override async register(server: CoreServer) {// ...const mockInvoiceController = new MockInvoiceController();server.internal.route([{method: RouteMethod.POST,path: `${this.getHybrisPath()}/users/{userId}/invoices`,handler: (request, response) => {return decorateController(request, response, () => {return mockInvoiceController.getUserInvoices.bind(mockInvoiceController)(request, response);});}}]);}}
Environment
-
Update the environment settings to mock the new route. In the
localenvironment, route mocking is enabled by default.shop/environment/local-stage.ts
hybris: {api: {mocked: ['cms/pages', 'messages', 'invoices'];}}