-
Notifications
You must be signed in to change notification settings - Fork 198
Unit Test Guidelines
Jest is used for unit tests. Check to see if there is an already existing unit test file (*.spec.ts
) in the tests folder for your capability. If there is, add your tests there. If there is not, please create a new test file.
- Initialization
- Call the function you want to test
- Confirm the function has been called
- If the function is sending a message over the SDK boundary, then you should mock the message coming back to the function from the boundary
- Confirm that the function you are testing behaved the way you expected (e.g., returned the value or threw the error you expected)
This is the same as validating an Error
thrown from inside a Promise
.
Use async
/await
and expect(...).rejects.toThrowError(errorMessage);
Note: prefer async
/await
to using return expect(...).rejects.toThrowError(errorMessage)
(note the return
before expect
) because it more closely resembles how app developers would call the function.
See jest expect.rejects documentation
Code to be tested
export function getCloudStorageFolders(channelId: string): Promise<CloudStorageFolder[]> {
return new Promise<CloudStorageFolder[]>(resolve => {
ensureInitialized(FrameContexts.content);
// test should validate this throws Error on null channelId
if (!channelId || channelId.length === 0) {
throw new Error('[files.getCloudStorageFolders] channelId name cannot be null or empty');
}
resolve(sendAndHandleError('files.getCloudStorageFolders', channelId));
});
}
Test
it('should not allow calls with null channelId', async () => {
await utils.initializeWithContext('content');
// use toThrowError
await expect(files.getCloudStorageFolders(null)).rejects.toThrowError(
'[files.getCloudStorageFolders] channelId name cannot be null or empty',
);
});
This is the same as validating an SdkError
is thrown from inside a Promise
.
Use async
/await
and expect(...).rejects.toEqual(expectedSdkErrorObject);
See jest toEqual documentation. Pay special attention to the note about toEqual
not performing a deep equality check for two errors. Since SdkError
is an object, we can use toEqual
to validate all the properties match.
Code to be tested
export function getLocation(
props: LocationProps,
callback?: (error: SdkError, location: Location) => void),
): Promise<Location> {
ensureInitialized(FrameContexts.content, FrameContexts.task);
return callCallbackWithErrorOrResultFromPromiseAndReturnPromise<Location>(
getLocationHelper,
callback,
props,
);
}
function getLocationHelper(props: LocationProps): Promise<Location> {
return new Promise<Location>(resolve => {
if (!isApiSupportedByPlatform(locationAPIsRequiredVersion)) {
throw { errorCode: ErrorCode.OLD_PLATFORM }; // validate this throws SdkError
}
if (!props) {
throw { errorCode: ErrorCode.INVALID_ARGUMENTS };
}
resolve(sendAndHandleError('location.getLocation', props));
});
}
Test
it('getLocation call in default version of platform support fails', async () => {
await mobilePlatformMock.initializeWithContext(FrameContexts.task);
mobilePlatformMock.setClientSupportedSDKVersion(originalDefaultPlatformVersion);
// use toEqual
await expect(location.getLocation(defaultLocationProps)).rejects.toEqual({
errorCode: ErrorCode.OLD_PLATFORM,
});
});
This is the same as validating an Error
is thrown from outside a Promise
.
Use expect(() => ...).toThrowError(errorMessage);
See jest toThrowError documentation. Since toEqual
doesn't perform a deep equality check on two Error
objects, we need to use toThrowError
instead.
Code to be tested
export function authenticate(authenticateParameters?: AuthenticateParameters): Promise<string> {
const isDifferentParamsInCall: boolean = authenticateParameters !== undefined;
const authenticateParams: AuthenticateParameters = isDifferentParamsInCall ? authenticateParameters : authParams;
if (!authenticateParams) {
throw new Error('No parameters are provided for authentication'); // validate this throws Error
... // omitted for clarity
}
Test
it('should not allow authentication.authenticate calls with no auth parameters', () => {
// use toThrowError
expect(() => authentication.authenticate()).toThrowError(
'No parameters are provided for authentication',
);
});
Generally speaking, use toThrowError
to validate an Error
is thrown, either in synchronous or async code.
If an SdkError
is thrown, prefer to use toEqual
to validate all properties match, either in synchronous or async code. (We cannot use toEqual
on an Error
because it does not perform a deep equality check on the two Error
objects.)
toMatchObject
will only check the object's properties that are passed to the toMatchObject
function, so in general, prefer greater specificity with toEqual
.
If you have a test that contains an assertion (e.g., expect(...).toEqual(...)
) that might not be executed, inside a catch
, for example, it will ensure that 'n' number of assertions were tested and will fail the test if that is not the case.
Note: We prefer to validate exceptions by the methods above (toThrowError
, rejects.toThrowError
, rejects.toEqual
, etc.) rather than expect.assertions
with validation in a catch
block.
See jest expect.assertions documentation.
Code to be tested
export interface MyError {
errorCode: ErrorCode;
}
export enum ErrorCode {
MY_ERROR_CODE = 1;
}
export function myFunction(arg: string): void {
if (arg === "myArgument") {
throw { errorCode: ErrorCode.MY_ERROR_CODE };
}
}
Test
it('should throw exception', () => {
expect.assertions(1);
expect(myFunction("myArgument"))
.catch(e => expect(e).toEqual({ errorCode: MY_ERROR_CODE });
}
We do not currently have official code coverage metrics. If you are running the Jest Visual Studio Code plugin, you can toggle the Coverage feature on, which highlights the lines of code that are not covered by the jest unit tests.
🔥 Hot Tip 🔥: Use this to help you decide if your unit tests are comprehensive!
- Open the VS Code Command Palette (Ctrl+Shift+P on Windows, ⌘+Shift+P on Mac)
- Type "Jest: Toggle Coverage"
In the status bar at the bottom of VS Code, you should now see this symbol and when you open a source file, you should be able to see the lines highlighted when they are missing full or partial coverage: