-
Notifications
You must be signed in to change notification settings - Fork 94
Unit Test Structure
In this article we will try to explain the expected way to write Unit Tests for the project. Generally the entire work starts from the test file, so we will start from there and go to closer scale. So once you create (be sure that you keep the original file position, but inside unit folder, and use [filename].unit.test.ts
name pattern) or find the existing test file which you would like to extend.
So you have the target file, but what to do next? Logically the test file has 3 levels, let's take a look to the chart:
So far you're expected to go level by level implementing the test.
Here we expect you to do 3 things mainly:
-
jest.mock
nodejsfs
module if it's used inside of the tested functionality; - Define
createGlobalMocks()
function (this one includes overrides for definitions of functions/methods in global packages of the app); - Define
describe()
blocks (simple, right? but there're still some rules);
createGlobalMocks()
is a function which has only one purpose - give you the centralised point to mock global dependencies. If you want to change the actual implementation of some function to jest.fn()
, it's the right place to do it.
There are two ways of doing it, though.
First, with transit object to access your mocks in the actual test:
function createGlobalMocks() {
const newVariables = {
showInfoMessage: jest.fn()
};
Object.defineProperty(vscode.window, "showInformationMessage", { value: newVariables.showInfoMessage });
return newVariables;
}
So you use this function in the actual it block and the returned object now is a source of all global definitions which you want to interact with.
Second, without transit object:
function createGlobalMocks() {
Object.defineProperty(vscode.window, "showInformationMessage", { value: jest.fn() });
}
It is a shorter definition, but it contains one problem: the TypeScript compiler needs to be told about the mocked function, so after the definition of the function you need to define another one:
const mocked = <T extends (...args: any[]) => any>(fn: T): jest.Mock<ReturnType<T>> => fn as any;
So you can wrap vscode.window.showInformationMessage
and use its mocked functionality.
Notice! Only one way is possible for the file, so if you extend existing tests, please, use the standard which is already implemented. If you add new tests you're free to choose yourself, but still keep in mind: one file - one standard.
For describe()
blocks we have only one requirement - naming standard. It's fairly simple: <module name> Unit Tests - Function <function name>
. Normally module name is related to the file where you do work.
In this section the whole work is going to happen inside the describe
block. Here we do two things:
- Define
createBlockMocks
; - (Optionally) restore function spies using
jest.restoreAllMocks()
; - Define
it()
blocks;
createBlockMocks()
is a sibling of createGlobalMocks()
but it always returns an object with mocks and you generate things which are related to the scope of the current test only. Let's say you need the Imperative Profile mock and Dataset Tree to be used as dependencies.
function createBlockMocks() {
const imperativeProfile = createIProfile();
const treeView = createTreeView();
return {
treeView,
imperativeProfile,
testDatasetTree: createDatasetTree(datasetSessionNode, treeView)
};
}
After you can just use your function inside of it()
block and utilise those mocks. Also you can see that we use so called creators; they are meant to be used inside of the above function, so if you need some mock check in creators folder first.
Also if you used the jest.spyOn()
method inside of your test it would good idea to add:
afterAll(() => jest.restoreAllMocks());
So your other tests won't be affected after the current describe()
block is finished.
So now you have all of your mock functions defined for both the global and block scopes. The only thing which is left is the actual test in it
. To get guidance about the actual test, you can read Best Practices: Unit Test guideline. The only thing which I would like you to pay attention is the structure:
it('Some test', async () => {
const globalMocks = createGlobalMocks();
const blockMocks = createBlockMocks();
// Next you can define your mocks reliable only for this test, like test nodes, spies and etc.
someFunctionWhichYouTest();
globalMocks.showInfoMessage.toBeCalled();
});
Notice that we used transit object for global mocks here. In the case of usage without this object, the test will look like the following:
it('Some test', async () => {
createGlobalMocks();
const blockMocks = createBlockMocks();
// Next you can define your mocks reliable only for this test, like test nodes, spies and etc.
someFunctionWhichYouTest();
mocked(vscode.window.showInformationMessage).toBeCalled();
});
If you are not sure about something, feel free to contact the community and ask about your tests!
zowe/vscode-extension-for-zowe
Welcome
Using Zowe Explorer
Roadmaps
Development Process
Testing process
Release Process
Backlog Grooming Process
How to Extend Zowe Explorer
- Extending Zowe Explorer
- Using Zowe Explorer Local Storage
- Error Handling for Extenders
- Secure Credentials for Extenders
- Sample Extender Repositories
Conformance Criteria
v3 Features and Information