Skip to content

Commit

Permalink
Try to implement test round abount empApi.
Browse files Browse the repository at this point in the history
  • Loading branch information
sfdcschwabe committed Dec 23, 2020
1 parent 7409e42 commit a331450
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 17 deletions.
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,60 @@
import { createElement } from 'lwc';
import FormulaShareRulesListView from 'c/formulaShareRulesListView';
import { registerLdsTestWireAdapter } from '@salesforce/sfdx-lwc-jest';
import { getTreeGridData } from '@salesforce/apex/FormulaShareRulesListViewController.getTreeGridData';
import getNamespacePrefix from '@salesforce/apex/FormulaShareUtilities.getNamespacePrefix';

import { jestMockPublish } from 'lightning/empApi';

// Register a test wire adapter.
const getTreeGridDataWireAdapter = registerLdsTestWireAdapter(getTreeGridData);
const getPrefixWireAdapter = registerLdsTestWireAdapter(getNamespacePrefix);

// Import mock data to send through the wire adapter.
const mockExampleTreeGridData = require('./data/exampleTreeGridData.json');
const mockPrefixData = require('./data/prefix.json');

// eslint-disable-next-line no-undef
const flushPromises = () => new Promise(setImmediate);

describe('c-formula-share-rules-list-view', () => {
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();
});

it('Test activate events (Positive).', async () => {
// Create initial lwc element and attach to virtual DOM.
const element = createElement('c-formula-share-rules-list-view', {
is: FormulaShareRulesListView
});
document.body.appendChild(element);

getPrefixWireAdapter.emit(mockPrefixData);
getTreeGridDataWireAdapter.emit(mockExampleTreeGridData);

// Make sure async subscribe call in connectedCallback completes
await flushPromises();

// connectedCallback is now complete, but no Platform Events have
// been published yet. Make assertions here about the state of your
// component prior to receiving the Platform Events.

// Mock-publish a Platform Event and await the promise
await jestMockPublish('/event/FormulaShare_List_Update__e', {
data: {
payload: {
Object_Label__c: 'Account',
Type__c: 'activate'
}
}
});
// Now any DOM updates that depend on the Platform Event should
// have rendered; assert about them here.
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import getTreeGridData from '@salesforce/apex/FormulaShareRulesListViewControlle
import recalculateSharing from '@salesforce/apex/FormulaShareRulesListViewController.recalculateSharing';
import activateDeactivate from '@salesforce/apex/FormulaShareRulesListViewController.activateDeactivate';
import getNamespacePrefix from '@salesforce/apex/FormulaShareUtilities.getNamespacePrefix';
import isContactSharingControlledByAccount from '@salesforce/apex/FormulaShareUtilities.isContactSharingControlledByAccount';


export default class TreeGrid extends NavigationMixin(LightningElement) {
Expand Down Expand Up @@ -142,7 +143,7 @@ export default class TreeGrid extends NavigationMixin(LightningElement) {
}

else if(error) {
//console.log('Error fetching data from Salesforce');
console.log('Error fetching data from Salesforce');
this.showError(error, 'Error fetching data from Salesforce');
}
}
Expand Down Expand Up @@ -187,43 +188,70 @@ export default class TreeGrid extends NavigationMixin(LightningElement) {
}
}

prefix;
@wire(getNamespacePrefix)
wiredNamespacePrefix({ error, data })
{
if (data) {
console.log('>>>data: ' + JSON.stringify(data, null, '\t'));

this.prefix = data;
} else if (error) {
console.error('>>>error: ' + error);
this.prefix = undefined;
console.log('Error getting namespace prefix');
this.showError(error, 'Error getting namespace prefix');
}
}

// Subcribes to list platform event, and refresh treegrid each time event is received
createOrUpdate = false;
manageRefreshEvents() {
console.log('>>>manageRefreshEvents.');

// Get namespace prefix
getNamespacePrefix()
.then((prefix) => {
//console.log('Got namespace: '+prefix);
/*getNamespacePrefix()
.then((prefix) => {*/
try {
console.log('Got namespace: '+ JSON.stringify(this.prefix, null, '\t'));

// Subscribe to list update events (raised by batch job and on rule activate/deactivate)
const listUpdateCallback = (response) => {
//console.log('Received Refresh Event');
console.log('Received Refresh Event');
this.refreshView();
};
subscribe('/event/'+prefix+'FormulaShare_List_Update__e', -1, listUpdateCallback).then(response => {
//console.log('Successfully subscribed to : ', JSON.stringify(response.channel));
console.log('#1');
subscribe('/event/'+this.prefix+'FormulaShare_List_Update__e', -1, listUpdateCallback)
.then(response => {
console.log('Successfully subscribed to : ', JSON.stringify(response.channel));
})
.catch(error => {
console.error('>>>error 2: ', JSON.stringify(error));
});

console.log('#2');
// Scubscribe to dml events (raised by on rule create/edit)
const dmlUpdateCallback = (response) => {
if(response.data.payload.Successful__c || response.data.payload.sdfs__Successful__c) {
//console.log('Received FormulaShare_Rule_DML__e');
console.log('Received FormulaShare_Rule_DML__e');
this.createOrUpdate = true;
this.refreshView();
}
};
console.log('#3');
subscribe('/event/'+prefix+'FormulaShare_Rule_DML__e', -1, dmlUpdateCallback).then(response => {
//console.log('List component subscribed to : ', JSON.stringify(response.channel));
console.log('List component subscribed to : ', JSON.stringify(response.channel));
});

})
.catch(error => {
//console.log('Error getting namespace prefix');
console.log('#4');
} catch (error) {
console.error('>>>error: ', JSON.stringify(error));
}

}
/*.catch(error => {
console.log('Error getting namespace prefix');
this.showError(error, 'Error getting namespace prefix');
});
}
}*/


// Set available drop-down actions for each grid row
Expand Down Expand Up @@ -376,19 +404,25 @@ export default class TreeGrid extends NavigationMixin(LightningElement) {
// Action method to update a rule to active/inactive
spinnerClasses;
activateDeactivate(row, actionName) {
console.log('>>>activateDeactivate.');
console.log('>>>row: ' + JSON.stringify(row, null, '\t'));
console.log('>>>actionName: ' + JSON.stringify(actionName, null, '\t'));

const rowDeveloperName = row['developerName'];
activateDeactivate({ ruleName : rowDeveloperName, type : actionName })
.then(() => {
console.log('then()');
this.processingLoad = true;
this.spinnerClasses = 'processingMessage';

// After submitting, wait 5 seconds and add class to display
setTimeout(() => {
this.spinnerClasses = 'processingMessage afterProcessingMessage';
}, 5000);
console.log('#1')
})
.catch(error => {
//console.log('Error changing activation status');
console.log('Error changing activation status');
this.showError(error, 'Error changing activation status')
});
}
Expand Down Expand Up @@ -423,6 +457,9 @@ export default class TreeGrid extends NavigationMixin(LightningElement) {

// Called to trigger a toast message including a system error
showError(error, toastTitle) {
console.log('>>>error: ' + JSON.stringify(error, null, '\t'));
console.log('>>>toastTitle: ' + JSON.stringify(toastTitle, null, '\t'));

let errorMessage = 'Unknown error';
if (Array.isArray(error.body)) {
errorMessage = error.body.map(e => e.message).join(', ');
Expand Down
33 changes: 33 additions & 0 deletions fs-core/test/jest-mocks/lightning/empApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@

// https://salesforce.stackexchange.com/questions/297563/test-lightning-emp-api
// An object to store callbacks
const _channels = {};

// On subscribe, store the callback function and resolve the promise
export const subscribe = jest.fn((channel, replayId, onMessageCallback) => {
_channels[channel] = { onMessageCallback };
Promise.resolve({

This comment has been minimized.

Copy link
@LawrenceLoz

LawrenceLoz Dec 26, 2020

Owner

Shout be prefixed with return, i.e. return Promise.respolve(...

id: "_" + Date.now(),
channel: channel,
replayId: replayId
});
});

// I'm using isEmpEnabled in my component, so I have it set to return true
export const isEmpEnabled = jest.fn().mockResolvedValue(true);

// A Jest-specific function for "publishing" your Platform Event
export const jestMockPublish = jest.fn((channel, message) => {
if (
_channels[channel] &&
_channels[channel].onMessageCallback instanceof Function
) {
_channels[channel].onMessageCallback(message);
}
Promise.resolve(true);

This comment has been minimized.

Copy link
@LawrenceLoz

LawrenceLoz Dec 26, 2020

Owner

Also needs a return prefix

});

// I just copied these from the standard lightning/empApi stub
export const unsubscribe = jest.fn().mockResolvedValue({});
export const onError = jest.fn().mockResolvedValue(jest.fn());
export const setDebugFlag = jest.fn().mockResolvedValue();
4 changes: 3 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ module.exports = {
'^lightning/uiRecordApi$':
'<rootDir>/fs-core/test/jest-mocks/lightning/uiRecordApi',
'^lightning/messageService$':
'<rootDir>/fs-core/test/jest-mocks/lightning/messageService'
'<rootDir>/fs-core/test/jest-mocks/lightning/messageService',
"^lightning/empApi$":
"<rootDir>/fs-core/test/jest-mocks/lightning/empApi"
},
//setupFiles: ['jest-canvas-mock'],
setupFilesAfterEnv,
Expand Down

9 comments on commit a331450

@sfdcschwabe
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@LawrenceLoz
Next challenge. This is the only(!) example I've found how to test empApi.

For test purposes I've separate the wire to get the prefix and the handling for the published event. I deliberately enabled console outputs to better respond to outputs.

My outputs looks like the following:
console.log
>>>manageRefreshEvents.

  at TreeGrid.manageRefreshEvents (fs-core/main/default/lwc/formulaShareRulesListView/formulaShareRulesListView.js:210:9)

console.log
Got namespace: undefined

  at TreeGrid.manageRefreshEvents (fs-core/main/default/lwc/formulaShareRulesListView/formulaShareRulesListView.js:216:21)

console.log
#1

  at TreeGrid.manageRefreshEvents (fs-core/main/default/lwc/formulaShareRulesListView/formulaShareRulesListView.js:223:17)

console.error
>>>error: {}

  244 |                 console.log('#4');
  245 |                 } catch (error) {
> 246 |                     console.error('>>>error: ', JSON.stringify(error));
      |                     ^
  247 |                 }
  248 |                 
  249 |             }

  at TreeGrid.manageRefreshEvents (fs-core/main/default/lwc/formulaShareRulesListView/formulaShareRulesListView.js:246:21)
  at TreeGrid.wireTreeData (fs-core/main/default/lwc/formulaShareRulesListView/formulaShareRulesListView.js:130:32)
  at node_modules/@lwc/engine/dist/engine.cjs.js:6492:14
  at runWithBoundaryProtection (node_modules/@lwc/engine/dist/engine.cjs.js:6410:5)
  at WireAdapterMock._dataCallback (node_modules/@lwc/engine/dist/engine.cjs.js:6490:5)
  at WireAdapterMock.emit (node_modules/@lwc/jest-preset/src/setup.js:110:18)
  at node_modules/@salesforce/wire-service-jest-util/dist/wire-service-jest-util.common.js:88:58

PASS fs-core/main/default/lwc/formulaShareRulesListView/tests/formulaShareRulesListViewEmpApi.test.js
c-formula-share-rules-list-view
✓ Test activate events (Positive). (69ms)

Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.842s, estimated 2s

The first problem was the cascading of different wire methods and how to prepopulate them for test purposes. It seems not very easy to do it in jest: https://salesforce.stackexchange.com/questions/292480/testing-lwc-with-multiple-wire-getrecord-calls-with-jest

The second problem: I've tried different approaches how to setup the prefix.json to emit the data for getNamespacePrefix.
the current approach is "", but then the namespace is undefined:
console.log
Got namespace: undefined

  at TreeGrid.manageRefreshEvents (fs-core/main/default/lwc/formulaShareRulesListView/formulaShareRulesListView.js:216:21)

When I change it to seems to work for the test: console.log
Got namespace: {
"prefix": ""
}

  at TreeGrid.manageRefreshEvents (fs-core/main/default/lwc/formulaShareRulesListView/formulaShareRulesListView.js:216:21)

But I don't think this is the way how the data for wire function works in production mode.

The third problem:
For some kind of reason an empty error appears:
console.error
>>>error: {}

  244 |                 console.log('#4');
  245 |                 } catch (error) {
> 246 |                     console.error('>>>error: ', JSON.stringify(error));
      |                     ^
  247 |                 }
  248 |                 
  249 |             }

  at TreeGrid.manageRefreshEvents (fs-core/main/default/lwc/formulaShareRulesListView/formulaShareRulesListView.js:246:21)
  at TreeGrid.wireTreeData (fs-core/main/default/lwc/formulaShareRulesListView/formulaShareRulesListView.js:130:32)
  at node_modules/@lwc/engine/dist/engine.cjs.js:6492:14
  at runWithBoundaryProtection (node_modules/@lwc/engine/dist/engine.cjs.js:6410:5)
  at WireAdapterMock._dataCallback (node_modules/@lwc/engine/dist/engine.cjs.js:6490:5)
  at WireAdapterMock.emit (node_modules/@lwc/jest-preset/src/setup.js:110:18)
  at node_modules/@salesforce/wire-service-jest-util/dist/wire-service-jest-util.common.js:88:58

To get more details and catch them in a different way I've added the try and catch block, but the error is still empty.

@LawrenceLoz
Copy link
Owner

Choose a reason for hiding this comment

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

Hey @sfdcschwabe,

  1. With the wire method mocking - actually I think your approach works here of mocking both wire methods separately. The issue you link to seems to affect situations where the same wire method is used in different ways in the same module - I think we're OK in this scenario because we're mocking two different apex methods with different names, and mocks seem to return appropriate results in the right places. Separating the resolving the namespace prefix in a different wire function in formulaShareRulesListView as you've done is a cleaner way of doing this I think so let's keep this approach.

  2. For the prefix, what do you think about avoiding complications around null / empty strings by always testing with the package namespace (i.e. mock response of "sdfs__")? The tests don't need an org to run (so no issues with expecting namespace in an org) and this is a helpful validation in the sense that it's a better test that the managed package will work correctly

  3. This took me a while to get my head around!. I think the issue with the error is actually quite simple - the functions subscribe() and jestMockPublish() in jest-mocks\lightning\empApi.js aren't actually returning the promises, I guess this is a typo on the stack exchange post. When I added these the code runs through the expected functions without throwing errors.

Hope you had a great Christmas. We'll be away for a while so I might be slow to respond but hope all is well

@sfdcschwabe
Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. Ok
  2. I'm fine with that approach and changed the mocking data to return the namespace prefix
  3. I've added the return statement to the empApi.js but the error still appears

I also think I've almost finished Jest Tests List View #10 and would ask you take a look at the corresponding branch in my fork. Let me know if there is something missing you think I should cover. After that I can create a pull request.

Happy New Year!!! :)

@LawrenceLoz
Copy link
Owner

@LawrenceLoz LawrenceLoz commented on a331450 Jan 3, 2021

Choose a reason for hiding this comment

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

In relation to point 3 above, the tests are failing because there's an exception thrown on line 231 which is caught, but error is not actually set. When you then call this.showError on line 237 this is passed to the function and line 437 fails because it's referencing a null (sorry should have mentioned I also fixed this when I had this running a few days ago - when I added the return statements the code ran through a little further than it had been doing and failed on this line, and then I added "this." to get this line working).

So needs a couple of things I think:

  • Checks for empty errors in showError function to make sure this won't fall over whatever is passed to it
  • Change prefix to this.prefix on line 231

Once this is fixed, one of the tests "Test activate/deactivate rule (Negative)" is failing because an assertion isn't satisfied. I haven't looked into why this is but let me know if you're having trouble with this.

Thanks and happy new year!

@LawrenceLoz
Copy link
Owner

Choose a reason for hiding this comment

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

And the branch looks good in general in terms of coverage and test cases. One thing which would be good to include is the package-lock.json which I think would mean it wouldn't be necessary to install jest (I'm not 100% sure on this so let me know if you think that's a bad idea for any reason)

When you're ready to send the pull request that would be great. I think it would be best to merge this into a release branch to go at the same time as other changes I'm working on - since API v50.0 is needed in the sfdx-project.json to run the tests it would be good to bring in these two changes into master together I think

@sfdcschwabe
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In relation to point 3 above, the tests are failing because there's an exception thrown on line 231 which is caught, but error is not actually set. When you then call this.showError on line 237 this is passed to the function and line 437 fails because it's referencing a null (sorry should have mentioned I also fixed this when I had this running a few days ago - when I added the return statements the code ran through a little further than it had been doing and failed on this line, and then I added "this." to get this line working).

So needs a couple of things I think:

  • Checks for empty errors in showError function to make sure this won't fall over whatever is passed to it
  • Change prefix to this.prefix on line 231

Once this is fixed, one of the tests "Test activate/deactivate rule (Negative)" is failing because an assertion isn't satisfied. I haven't looked into why this is but let me know if you're having trouble with this.

Thanks and happy new year!

This is why I find investigating bugs in javascript daunting. Such a small problem and such a big impact, with no meaningful clue as to the cause of the error. Your hint fix the error and the test 'Test activate events (Positive).' in 'fs-core/main/default/lwc/formulaShareRulesListView/tests/formulaShareRulesListViewEmpApi.test.js' ran smoothly. 👍
Thank you!

I think we fix null or empty errors with the issue #19

@sfdcschwabe
Copy link
Contributor Author

Choose a reason for hiding this comment

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

And the branch looks good in general in terms of coverage and test cases. One thing which would be good to include is the package-lock.json which I think would mean it wouldn't be necessary to install jest (I'm not 100% sure on this so let me know if you think that's a bad idea for any reason)

When you're ready to send the pull request that would be great. I think it would be best to merge this into a release branch to go at the same time as other changes I'm working on - since API v50.0 is needed in the sfdx-project.json to run the tests it would be good to bring in these two changes into master together I think

Could you provide a release branch for it?

I also want to know more about package-lock.json but couldn't find it in my repository (anymore) and researched about it. In the summary it is described that this file should be added to the repository.
Here is also a blog from salesforce, too. See section 'Working with node packages' to understand the backgrounds of this file. In summary: I was able to rebuild package-lock.json by executing 'npm install' again.

@LawrenceLoz
Copy link
Owner

Choose a reason for hiding this comment

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

Sure I've made a release branch here and merged in the work I've done so far: https://github.com/LawrenceLoz/FormulaShare-DX/tree/Release_Feb21

Interesting that package-lock had gone from your repo but you were still able to run jest tests. Sounds good to include though and thanks for the link - looks like including should at least ensure we're both using consistent node configurations.

@LawrenceLoz
Copy link
Owner

Choose a reason for hiding this comment

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

And I'm with you on Javascript debugging! Find myself often much more confused when things go wrong and resorting to browser debugging and breakpoints to track down confusing unhelpful error messages.

Sounds good to address null and empty errors with issue 19

Please sign in to comment.