Step Three: Identifying Test Cases
Identifying the right code to test can be challenging. You'll need a general understanding of what your script does and its expected output. Before you start identifying test cases, you should be familiar with mocking. Mocking allows you to isolate a unit of code and mimic its behavior.
Mocking
Mocking is the process of creating arbitrary values to imitate the behavior of the script. In SuiteScript, we can mock all object and module members that are used in a script. This could include methods, objects, and properties.
We imitate the behavior of SuiteScript dependencies to provide context to Jest. To imitate this behavior, we use built in Jest functions that allow us to create a variation of outcomes.
Mocking SuiteScript Methods and Objects
To mock a SuiteScript method, you will need to use a Jest mock function to initiate a test. You should familiarize yourself with Jest mock functions to have a better understanding of mocking. For a complete list of Jest mock functions, see Jest Mock Functions.
Mocking Method Examples
The following code sample will mock the runtime.getCurrentScript() method with the mockReturnValue() Jest mock function. This mock function allows you to return an arbitrary value for this method. Note that this method returns a runtime.Script object, which we'll use as the mock return value. This object is represented as Script
in the following example, because that is how it was defined. Note that you can name this object to your liking.
import runtime from 'N/runtime'; // import runtime module
import Script from 'N/runtime/script'; // import script object from runtime module
describe('Mocking SuiteScript dependencies', () => {
it('Should mock Suitescript method', () => {
// given
runtime.getCurrentScript.mockReturnValue(Script); //returns runtime.Script object
// then
expect(runtime.getCurrentScript).toHaveBeenCalled();
});
});
The following code sample will mock the Script.getParameter(options) method with the mockImplementation() Jest mock function. This method returns any number, date, string, boolean, or null value depending on script logic. This example shows the number 15 as the value returned to represent an internal ID. It is important to note that you can set this value to any of the types mentioned in your test. Any arbitrary value will work.
import runtime from 'N/runtime'; // import runtime module from runtime.js stub file
import Script from 'N/runtime/script'; // import script object from Script.js stub file
describe('Mocking SuiteScript dependencies', () => {
it('Should mock Suitescript method', () => {
// given
runtime.getCurrentScript.mockReturnValue(Script);
Script.getParameter.mockImplementation(options => options.name === 'custscript_example_test' && 15); // returns 15
// then
expect(runtime.getCurrentScript).toHaveBeenCalled();
expect(Script.getParameter).toHaveBeenCalledWith({name: 'custscript_example_test'});
expect(Script.getParameter).toHaveReturnedWith(15);
});
});
Mocking SuiteScript Properties
To mock a SuiteScript property, set the value of the property to an arbitrary value in your test file. For best practice, you'll want to use a valid value type that's accepted for the SuiteScript property used. For example, the Record.type property accepts string values, so your mocked value should be a string.
describe('Mocking SuiteScript dependencies', () => {
it('Should mock Suitescript property', () => {
// given
record.create.mockReturnValue(Record);
Record.type = 'salesorder';
});
});
Handling Enums
SuiteScript enums must be defined in the test file as an object with the exact values that are used in the script file. It is not required to list every possible value of the enum. If you don't define the enum with the exact values, an error will occur in your test file.
import script from "../src/FileCabinet/SuiteScripts/ue_exampleScript";
// import modules used
import record from 'N/recprd';
import Record from 'N/record/instance';
beforeEach(() => {
jest.clearAllMocks();
});
record.Type = {
SALES_ORDER: 'SALES_ORDER' // only define values used in your script
};
describe('Mocking SuiteScript dependencies', () => {
it('Should do something', () => {
// test code goes here
});
});
Mocking Entry Point Functions
A simple starting point for writing tests could be verifying the entry point functions. Each entry point function has required and optional parameters. You can format your test to ensure the parameters have the correct values and types. For more information about entry point functions, see SuiteScript 2.x Script Types and Entry Points.
The following example shows an object that represents a user event script that will execute on the beforeLoad(context) entry point. The properties of this object are the assigned parameters of the beforeLoad entry point.
User Event Script example:
import runtime from 'N/runtime';
import Script from 'N/runtime/script';
import record from 'N/record';
import Record from 'N/record/instance';
const context = {
UserEventType: { // beforeLoad entry point properties
CREATE: 'CREATE',
EDIT: 'EDIT',
VIEW: 'VIEW'
}
};
describe('Mocking SuiteScript dependencies', () => {
it('Should do something', () => {
});
});
Suitelet Example:
import runtime from 'N/runtime';
import Script from 'N/runtime/script';
import record from 'N/record';
import Record from 'N/record/instance';
const scriptContext = {
request: {
parameters: {
record_id: 14,
sales_order_id: 325
}
};
describe('Mocking SuiteScript dependencies', () => {
it('Should do something', () => {
// test code goes here
});
});
Testing Conditional Statements
Be mindful of testing conditional statements as well. Conditional statements are automatically evaluated with Jest, so be sure to write tests that verify the logic.
The following example writes a test to verify that a client script with the validateLine(scriptContext) entry point function is being called with the correct parameters and value types. The script checks the scriptContext.sublistId
value in the conditional statement. With this information, we can structure the unit test by setting this parameter value to pass or fail the condition.
exampleScript.js:
define(['N/record', 'N/runtime', 'N/log'], (record, runtime, log) => {
function validateLine(scriptContext) {
const recSalesOrder = scriptContext.currentRecord;
if (scriptContext.sublistId === 'item') {
const casePerPallet = recSalesOrder.getCurrentSublistValue({
sublistId: 'item',
fieldId: 'custcol_cases_per_pallet'
});
const quantity = recSalesOrder.getCurrentSublistValue({
sublistId: 'item',
fieldId: 'quantity'
});
}
}
exampleScript.test.js
The condition will fail in the example test file below because the sublistId
is incorrect. Therefore we can expect the CurrentRecord.getSublistValue(options) method to not be called.
describe('Testing conditional statements', () => {
it('Should test validateLine function parameters', () => {
// given
scriptContext.currentRecord = CurrentRecord; // CurrentRecordObj
scriptContext.sublistId = 'name'; // condition fails
// when
script.validateLine(scriptContext);
// then
expect(CurrentRecord.getCurrentSublistValue).not.toHaveBeenCalled(); // function should not be called
});
});
The condition will pass in the example test file below because the sublistId
is correct. Therefore we can expect the methods to be called twice.
describe('Testing conditional statements', () => {
it('Should test validateLine function parameters', () => {
// given
scriptContext.currentRecord = CurrentRecord; // CurrentRecordObj
scriptContext.sublistId = 'item'; // condition passes
// when
script.validateLine(scriptContext);
// then
expect(CurrentRecord.getCurrentSublistValue).toHaveBeenCalledTimes(2); // function will be called
});