Skip to content
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

Merged
merged 9 commits into from Feb 19, 2019
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -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();
Copy link
Contributor

Choose a reason for hiding this comment

The 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('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();
Copy link
Contributor

Choose a reason for hiding this comment

The 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();
});
});
});
@@ -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();
Copy link
Contributor

Choose a reason for hiding this comment

The 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'));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're updating the input here to set the searchKey, which will be passed as a param to findContacts after the button is clicked, but we never use or verify the searchKey value.

It'd be nice to have a test case for that. Since findContacts is a Jest mock function we should be able to verify the parameters on the invocation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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();
Copy link
Contributor

Choose a reason for hiding this comment

The 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();
});
});
});
@@ -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';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@salesforce/lwc-jest version 0.4.10 has some exciting new updates that are applicable here.

  1. The wire-service-jest-util APIs should be imported directly from @salesforce/lwc-jest now.
  2. For emitting data/errors through an @wire that is connected to an Apex method, use the new test utility registerApexTestWireAdapter.

See trailheadapps/ebikes-lwc#30 for an example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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();
});
});
});
});
@@ -0,0 +1,5 @@
{
"Name": "Amy Taylor",
"Title": "VP of Engineering",
"Email": "amy@demo.net"
}
@@ -0,0 +1 @@
{}
@@ -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';
Copy link
Contributor

Choose a reason for hiding this comment

The 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 registerApexTestWireAdapter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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();
});
});
});
});
@@ -0,0 +1 @@
[{ "Id": "99", "Name": "Amy Taylor" }, { "Id": "22", "Name": "Jeff Taylor" }]
@@ -0,0 +1 @@
[]