New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Jest tests for Apex recipes #58
Changes from 8 commits
0f807e3
d39b70e
77715e2
cb7a567
94bc9c8
91cec9c
e0ed4af
f715e6e
53e93f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { createElement } from 'lwc'; | ||
import ApexImperativeMethod from 'c/apexImperativeMethod'; | ||
import getContactList from '@salesforce/apex/ContactController.getContactList'; | ||
|
||
// Mocking impertive Apex method call | ||
jest.mock( | ||
'@salesforce/apex/ContactController.getContactList', | ||
() => { | ||
return { | ||
default: jest.fn() | ||
}; | ||
}, | ||
{ virtual: true } | ||
); | ||
|
||
// Sample data for imperative Apex call | ||
const APEX_CONTACTS_SUCCESS = [ | ||
{ Id: '99', Name: 'Amy Taylor' }, | ||
{ Id: '22', Name: 'Jeff Taylor' } | ||
]; | ||
const APEX_CONTACTS_ERROR = { | ||
body: { message: 'An internal server error has occurred' }, | ||
ok: false, | ||
status: 400, | ||
statusText: 'Bad Request' | ||
}; | ||
|
||
describe('c-apex-imperative-method', () => { | ||
beforeAll(() => { | ||
jest.useFakeTimers(); | ||
}); | ||
|
||
afterEach(() => { | ||
// The jsdom instance is shared across test cases in a single file so reset the DOM | ||
while (document.body.firstChild) { | ||
document.body.removeChild(document.body.firstChild); | ||
} | ||
// Prevent data saved on mocks from leaking between tests | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
// Helper function to wait until the microtask queue is empty. This is needed for promise timing when calling Apex | ||
function flushPromises() { | ||
// eslint-disable-next-line no-undef | ||
return new Promise(resolve => setImmediate(resolve)); | ||
} | ||
|
||
it('renders two contacts returned from imperative Apex call', () => { | ||
getContactList.mockResolvedValue(APEX_CONTACTS_SUCCESS); | ||
|
||
// Create initial element | ||
const element = createElement('c-apex-imperative-method', { | ||
is: ApexImperativeMethod | ||
}); | ||
document.body.appendChild(element); | ||
|
||
// Select button for executing Apex call | ||
const buttonEl = element.shadowRoot.querySelector('lightning-button'); | ||
buttonEl.dispatchEvent(new CustomEvent('click')); | ||
|
||
return flushPromises().then(() => { | ||
// Select div for conditionally changed text content | ||
const detailEls = element.shadowRoot.querySelectorAll( | ||
'p:not([class])' | ||
); | ||
expect(detailEls.length).toBe(2); | ||
expect(detailEls[0].textContent).toBe('Amy Taylor'); | ||
expect(detailEls[1].textContent).toBe('Jeff Taylor'); | ||
}); | ||
}); | ||
|
||
it('renders the error panel when the Apex method returns an error', () => { | ||
getContactList.mockRejectedValue(APEX_CONTACTS_ERROR); | ||
|
||
// Create initial element | ||
const element = createElement('c-apex-imperative-method', { | ||
is: ApexImperativeMethod | ||
}); | ||
document.body.appendChild(element); | ||
|
||
// Select button for executing Apex call | ||
const buttonEl = element.shadowRoot.querySelector('lightning-button'); | ||
buttonEl.dispatchEvent(new CustomEvent('click')); | ||
|
||
jest.runAllTimers(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove |
||
|
||
// Return an immediate flushed promise (after the Apex call) to then | ||
// wait for any asynchronous DOM updates. Jest will automatically wait | ||
// for the Promise chain to complete before ending the test and fail | ||
// the test if the promise ends in the rejected state | ||
return flushPromises().then(() => { | ||
const errorPanelEl = element.shadowRoot.querySelector( | ||
'c-error-panel' | ||
); | ||
expect(errorPanelEl).not.toBeNull(); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import { createElement } from 'lwc'; | ||
import ApexImperativeMethodWithParams from 'c/apexImperativeMethodWithParams'; | ||
import findContacts from '@salesforce/apex/ContactController.findContacts'; | ||
|
||
// Mocking impertive Apex method call | ||
jest.mock( | ||
'@salesforce/apex/ContactController.findContacts', | ||
() => { | ||
return { | ||
default: jest.fn() | ||
}; | ||
}, | ||
{ virtual: true } | ||
); | ||
|
||
// Sample data for imperative Apex call | ||
const APEX_CONTACTS_SUCCESS = [{ Id: '99', Name: 'Amy Taylor' }]; | ||
const APEX_CONTACTS_ERROR = { | ||
body: { message: 'An internal server error has occurred' }, | ||
ok: false, | ||
status: 400, | ||
statusText: 'Bad Request' | ||
}; | ||
|
||
describe('c-apex-imperative-method-with-params', () => { | ||
beforeAll(() => { | ||
jest.useFakeTimers(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove |
||
}); | ||
|
||
afterEach(() => { | ||
// The jsdom instance is shared across test cases in a single file so reset the DOM | ||
while (document.body.firstChild) { | ||
document.body.removeChild(document.body.firstChild); | ||
} | ||
// Prevent data saved on mocks from leaking between tests | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
// Helper function to wait until the microtask queue is empty. This is needed for promise timing when calling Apex | ||
function flushPromises() { | ||
// eslint-disable-next-line no-undef | ||
return new Promise(resolve => setImmediate(resolve)); | ||
} | ||
|
||
it('passes the user input to the Apex method correctly', () => { | ||
const USER_INPUT = 'Taylor'; | ||
const APEX_PARAMETERS = { searchKey: USER_INPUT }; | ||
findContacts.mockResolvedValue(APEX_CONTACTS_SUCCESS); | ||
|
||
// Create initial element | ||
const element = createElement('c-apex-imperative-method-with-params', { | ||
is: ApexImperativeMethodWithParams | ||
}); | ||
document.body.appendChild(element); | ||
|
||
const inputEl = element.shadowRoot.querySelector('lightning-input'); | ||
inputEl.value = USER_INPUT; | ||
inputEl.dispatchEvent(new CustomEvent('change')); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're updating the input here to set the It'd be nice to have a test case for that. Since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added. |
||
|
||
// Select button for executing Apex call | ||
const buttonEl = element.shadowRoot.querySelector('lightning-button'); | ||
buttonEl.dispatchEvent(new CustomEvent('click')); | ||
|
||
return flushPromises().then(() => { | ||
expect(findContacts.mock.calls[0][0]).toEqual(APEX_PARAMETERS); | ||
}); | ||
}); | ||
|
||
it('renders one contact', () => { | ||
const USER_INPUT = 'Taylor'; | ||
const USER_RESULT = 'Amy Taylor'; | ||
findContacts.mockResolvedValue(APEX_CONTACTS_SUCCESS); | ||
|
||
// Create initial element | ||
const element = createElement('c-apex-imperative-method-with-params', { | ||
is: ApexImperativeMethodWithParams | ||
}); | ||
document.body.appendChild(element); | ||
|
||
const inputEl = element.shadowRoot.querySelector('lightning-input'); | ||
inputEl.value = USER_INPUT; | ||
inputEl.dispatchEvent(new CustomEvent('change')); | ||
|
||
// Select button for executing Apex call | ||
const buttonEl = element.shadowRoot.querySelector('lightning-button'); | ||
buttonEl.dispatchEvent(new CustomEvent('click')); | ||
|
||
return flushPromises().then(() => { | ||
// Select div for conditionally changed text content | ||
const detailEls = element.shadowRoot.querySelectorAll('p'); | ||
expect(detailEls.length).toBe(1); | ||
expect(detailEls[0].textContent).toBe(USER_RESULT); | ||
}); | ||
}); | ||
|
||
it('renders the error panel when the Apex method returns an error', () => { | ||
findContacts.mockRejectedValue(APEX_CONTACTS_ERROR); | ||
|
||
// Create initial element | ||
const element = createElement('c-apex-imperative-method-with-params', { | ||
is: ApexImperativeMethodWithParams | ||
}); | ||
document.body.appendChild(element); | ||
|
||
// Select button for executing Apex call | ||
const buttonEl = element.shadowRoot.querySelector('lightning-button'); | ||
buttonEl.dispatchEvent(new CustomEvent('click')); | ||
|
||
jest.runAllTimers(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove |
||
|
||
// Return an immediate flushed promise (after the Apex call) to then | ||
// wait for any asynchronous DOM updates. Jest will automatically wait | ||
// for the Promise chain to complete before ending the test and fail | ||
// the test if the promise ends in the rejected state | ||
return flushPromises().then(() => { | ||
const errorPanelEl = element.shadowRoot.querySelector( | ||
'c-error-panel' | ||
); | ||
expect(errorPanelEl).not.toBeNull(); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import { createElement } from 'lwc'; | ||
import ApexStaticSchema from 'c/apexStaticSchema'; | ||
import { registerApexTestWireAdapter } from '@salesforce/lwc-jest'; | ||
import getSingleContact from '@salesforce/apex/ContactController.getSingleContact'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
See trailheadapps/ebikes-lwc#30 for an example. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed. |
||
|
||
// Realistic data with a single record | ||
const mockGetSingleContact = require('./data/getSingleContact.json'); | ||
// An empty list of records to verify the component does something reasonable | ||
// when there is no data to display | ||
const mockGetSingleContactNoRecord = require('./data/getSingleContactNoRecord.json'); | ||
|
||
// Register as an Apex wire adapter. Some tests verify that provisioned values trigger desired behavior. | ||
const getSingleContactAdapter = registerApexTestWireAdapter(getSingleContact); | ||
|
||
describe('c-apex-static-schema', () => { | ||
afterEach(() => { | ||
// The jsdom instance is shared across test cases in a single file so reset the DOM | ||
while (document.body.firstChild) { | ||
document.body.removeChild(document.body.firstChild); | ||
} | ||
// Prevent data saved on mocks from leaking between tests | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
describe('getSingleContact @wire data', () => { | ||
it('with single record', () => { | ||
const USER_RESULT = 'Amy Taylor'; | ||
const TITLE_RESULT = 'VP of Engineering'; | ||
const EMAIL_RESULT = 'amy@demo.net'; | ||
|
||
const element = createElement('c-apex-static-schema', { | ||
is: ApexStaticSchema | ||
}); | ||
document.body.appendChild(element); | ||
getSingleContactAdapter.emit(mockGetSingleContact); | ||
|
||
// Return a promise to wait for any asynchronous DOM updates. | ||
return Promise.resolve().then(() => { | ||
const detailEls = element.shadowRoot.querySelectorAll('p'); | ||
expect(detailEls[0].textContent).toBe(USER_RESULT); | ||
expect(detailEls[1].textContent).toBe(TITLE_RESULT); | ||
|
||
const emailEl = element.shadowRoot.querySelector( | ||
'lightning-formatted-email' | ||
); | ||
expect(emailEl.value).toBe(EMAIL_RESULT); | ||
}); | ||
}); | ||
|
||
it('with no record', () => { | ||
const element = createElement('c-apex-static-schema', { | ||
is: ApexStaticSchema | ||
}); | ||
document.body.appendChild(element); | ||
getSingleContactAdapter.emit(mockGetSingleContactNoRecord); | ||
|
||
// Return a promise to wait for any asynchronous DOM updates. | ||
return Promise.resolve().then(() => { | ||
const detailEls = element.shadowRoot.querySelectorAll('p'); | ||
expect(detailEls[0].textContent).toBe(''); | ||
expect(detailEls[1].textContent).toBe(''); | ||
|
||
const emailEl = element.shadowRoot.querySelector( | ||
'lightning-formatted-email' | ||
); | ||
expect(emailEl.value).toBeUndefined(); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('getSingleContact @wire error', () => { | ||
it('shows error panel element', () => { | ||
const element = createElement('c-apex-static-schema', { | ||
is: ApexStaticSchema | ||
}); | ||
document.body.appendChild(element); | ||
getSingleContactAdapter.error(); | ||
return Promise.resolve().then(() => { | ||
const errorPanelEl = element.shadowRoot.querySelector( | ||
'c-error-panel' | ||
); | ||
expect(errorPanelEl).not.toBeNull(); | ||
}); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"Name": "Amy Taylor", | ||
"Title": "VP of Engineering", | ||
"Email": "amy@demo.net" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { createElement } from 'lwc'; | ||
import ApexWireMethodToFunction from 'c/apexWireMethodToFunction'; | ||
import { registerApexTestWireAdapter } from '@salesforce/lwc-jest'; | ||
import getContactList from '@salesforce/apex/ContactController.getContactList'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment as apexStaticSchema.test.js above. After upgrading to lwc-jest, import from lwc-jest and use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed. |
||
|
||
// Realistic data with a list of contacts | ||
const mockGetContactList = require('./data/getContactList.json'); | ||
// An empty list of records to verify the component does something reasonable | ||
// when there is no data to display | ||
const mockGetContactListNoRecords = require('./data/getContactListNoRecords.json'); | ||
|
||
// Register as an Apex wire adapter. Some tests verify that provisioned values trigger desired behavior. | ||
const getContactListAdapter = registerApexTestWireAdapter(getContactList); | ||
|
||
describe('c-apex-wire-method-to-function', () => { | ||
afterEach(() => { | ||
// The jsdom instance is shared across test cases in a single file so reset the DOM | ||
while (document.body.firstChild) { | ||
document.body.removeChild(document.body.firstChild); | ||
} | ||
// Prevent data saved on mocks from leaking between tests | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
describe('getContactList @wire data', () => { | ||
it('with two records', () => { | ||
const USER1_RESULT = 'Amy Taylor'; | ||
const USER2_RESULT = 'Jeff Taylor'; | ||
|
||
const element = createElement('c-apex-wire-method-to-function', { | ||
is: ApexWireMethodToFunction | ||
}); | ||
document.body.appendChild(element); | ||
getContactListAdapter.emit(mockGetContactList); | ||
return Promise.resolve().then(() => { | ||
const detailEls = element.shadowRoot.querySelectorAll('p'); | ||
expect(detailEls.length).toBe(2); | ||
expect(detailEls[0].textContent).toBe(USER1_RESULT); | ||
expect(detailEls[1].textContent).toBe(USER2_RESULT); | ||
}); | ||
}); | ||
|
||
it('with no record', () => { | ||
const element = createElement('c-apex-wire-method-to-function', { | ||
is: ApexWireMethodToFunction | ||
}); | ||
document.body.appendChild(element); | ||
getContactListAdapter.emit(mockGetContactListNoRecords); | ||
return Promise.resolve().then(() => { | ||
const detailEls = element.shadowRoot.querySelectorAll('p'); | ||
expect(detailEls.length).toBe(0); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('getContactList @wire error', () => { | ||
it('shows error panel element', () => { | ||
const element = createElement('c-apex-wire-method-to-function', { | ||
is: ApexWireMethodToFunction | ||
}); | ||
document.body.appendChild(element); | ||
getContactListAdapter.error(); | ||
return Promise.resolve().then(() => { | ||
const errorPanelEl = element.shadowRoot.querySelector( | ||
'c-error-panel' | ||
); | ||
expect(errorPanelEl).not.toBeNull(); | ||
}); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
[{ "Id": "99", "Name": "Amy Taylor" }, { "Id": "22", "Name": "Jeff Taylor" }] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
[] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove