Mocking
Building LWCs locally that point to Apex Methods, 3rd Party AppExchange packages and On Platform specific JavaScript imports prevent local development from being feasible without considerable effort.
Example (via lwc.config.json
)
LWC Garden utalises the lwc.config.json
file as a base for mocking locally.
Directory__mocks__
Directory@salesforce
Directorycommunity
- basePath.js
Directoryapex
- ExampleController.getCurrentTime.js
- ExampleController.getAccount.js
- …
- …
Directoryforce-app
Directorymain
Directorydefault
Directorylwc
DirectoryexampleComponent
- garden.config.js
- exampleComponent.css
- exampleComponent.html
- exampleComponent.js
- …
- lwr.config.json
{ "modules": [ { "dir": "./.garden/components", "namespace": "garden" }, { "dir": "./components", "namespace": "component" }, { "name": "@salesforce/community/basePath", "path": "./__mocks__/@salesforce/community/basePath.js" }, { "name": "@salesforce/apex/ExampleController.getCurrentTime", "path": "./__mocks__/apex/ExampleController.getCurrentTime.js" }, { "name": "@salesforce/apex/ExampleController.getAccount", "path": "./__mocks__/apex/ExampleController.getAccount.js" } ]}
import { LightningElement, wire } from 'lwc'
import basePath from '@salesforce/community/basePath'
import apex_getCurrentTime from '@salesforce/apex/ExampleController.getCurrentTime'import apex_getAccount from '@salesforce/apex/ExampleController.getAccount'
export default class SalesforceImports extends LightningElement { basePath = basePath accountId
@wire(apex_getAccount, { accountId: '$accountId', }) wiredGetById({ error, data }) { if (error) { // TODO: handle error } else if (data) { // TODO: handle data } }
handleGetCurrentTimeFromApex = async () => { const response = await apex_getCurrentTime() // TODO: handle response }}
Imperative Apex
Imperative Apex mocks are super simple. The file should export default
a single method. You may choose to run custom logic here to make your local development “more dynamic” or choose to return a hardcoded response.
Wire Apex
Wire Apex mocks are a bit more complex. To build these successfully, you must follow wire adapter structure.
export default class getAccount { connected = false accountId
constructor(dataCallback) { this.dataCallback = dataCallback }
connect() { this.connected = true this.runMethod() }
disconnect() { this.connected = false }
update(config) { // if any of the wire values have changed if ( this.isDiff(config, 'accountId') || this.isDiff(config, 'publishStatus') ) { this.accountId = config.accountId
this.runMethod() } }
isDiff = (config, key) => this[key] !== config[key]
runMethod() { const { connected, accountId } = this
if (connected && !!accountId) { // pass back mock for "valid" accountId values // '001' is how all accountIds start - using for testing here if (accountId.startsWith('001')) { this.dataCallback({ data: { Id: accountId, Name: 'LWC Garden Account', }, error: undefined, }) } else { this.dataCallback({ data: undefined, error: { message: 'Invalid accountId. AccountIds start with "001"', }, }) } } }}
Check out lwc.dev/guide/wire_adapter for more information on wire adapters.
On Platform Imports
Imports like the following are the most straight forward to mock. They require returning a single value, no functions and no @wire
adapter boilerplate.
@salesforce/client/formFactor
->export default 'Small'
@salesforce/community/basePath
->export default 'community-path'
@salesforce/community/Id
->export default '09a300012345678ABC'
@salesforce/user/Id
->export default '005300012345678DEF'
Given the simplicity of these imports, you may return function responses here too, again if you wish to make your imports more dynamic.
Here is a mock of the @salesforce/client/formFactor
import. Here we return either Small
or Large
based on the window.innerWidth
size to better mimic the on-platform functionality.
const formFactor = () => (window.innerWidth < 768 ? 'Small' : 'Large')export default formFactor()
Components and Third Party Packages
A common third party package to use when building on the Salesforce platform is OmniStudio. OmniStudio allows you to build custom LWCs on the OmniScript platform. These can be either standalone LWCs or LWCs that extend OmniScript base components.
Building on top of OmniScript components can cause issues when developing locally. LWC Garden allows you to mock these components locally and not have to worry about the components not being present in your local environment.
Example with OmniScript
In this example we’ll be mocking the OmniScript Card so we can build our own extension of the component locally.
Before we dive in, lets have a quick look at the folder structure:
Directory__mocks__
Directoryomniscript
DirectoryomniscriptBlock
- omniscriptBlock.html
- omniscriptBlock.js
- …
- …
Directoryforce-app
Directorymain
Directorydefault
Directorylwc
DirectorycustomOmniCard
- garden.config.js
- customOmniCard.css
- customOmniCard.html
- customOmniCard.js
- …
- lwr.config.json
Configure your lwr.config.json
file to point to the __mocks__/omniscript
folder.
{ "lwc": { "modules": [ { "dir": "./.garden/components", "namespace": "garden" }, { "dir": "./local", "namespace": "local" }, { "dir": "./__mocks__/omnistudio", "namespace": "omnistudio" } ] }}
OmniscriptBlock LWC (Mock)
import { LightningElement } from 'lwc'import html_template from './omniscriptBlock.html'
export default class OmniscriptBlock extends LightningElement { render() { return html_template }}
<template> <div> <slot></slot> </div></template>
CustomOmniCard (Implementation)
This is our custom LWC that extends the OmniscriptBlock
imported from the omnistudio
namespace. With our above config, this will resolve to our local ./__mocks__/omnistudio/omniscriptBlock
LWC.
import OmniscriptBlock from 'omnistudio/omniscriptBlock'import html_template from './customOmniCard.html'
export default class CustomOmniCard extends OmniscriptBlock { render() { return html_template }}