Before you get stuck into this make sure you've checked out any previous parts to the series. Each part in this series follows on from the previous, so you may need to grab the code from the previous part if you haven't been following.
Unit Tests? But my code is next level!
Nowadays there is no excuse to not unit test your code. It's becoming easy and more valuable, so don't not do it!
In this post I'm going to provide a basic guide to get you started with unit tests and typescript webresources for Dynamics 365... err Dataverse... err Power Apps... or whatever we are calling it today!
Configure Jest
First of all lets install the jest npm packages we require:
npm install jest @types/jest ts-jest --save-dev
Next we'll create a basic config via:
npx ts-jest config:init
Finally we'll install xrm-mock and sinon to help us a little:
npm install xrm-mock sinon @types/sinon --save-dev
Ensure your package.json
is setup to run jest via npm run test
makes sure you have the following script defined:
"scripts": {
"test": "jest",
...
Now we are ready to write some tests! :-D
Writing our first test
Create a folder in the root of the project called "tests" and then create a new file called "first.test.ts".
Paste the following into the new file:
import { NavigationStaticMock, XrmMockGenerator } from "xrm-mock";
import * as sinon from "sinon";
import { Pointless } from "../src/sample";
describe("sample test", () => {
describe("Pointless", () => {
test("Should display alert", () => {
XrmMockGenerator.initialise();
const stub = sinon.stub(
NavigationStaticMock.prototype,
"openAlertDialog"
);
const msg = "a pointless test message";
Pointless(msg);
expect(stub.called).toBeTruthy();
expect(stub.calledOnce).toBeTruthy();
expect(stub.firstCall.args[0].text).toBe(msg);
expect(stub.firstCall.args[0].title).toBe("A Pointless Message");
});
});
});
Lets have a closer look at the test...
I'm not going to tell you how to use jest and sinon as these already have great documentation of their own.
First we'll "Arrange" our test...
Initialise our global Xrm
object:
XrmMockGenerator.initialise();
Stub openAlertDialog()
. As I expect you know, openAlertDialog()
displays a dialog in D365. Stubbing the function enables the code to execute without error given we don't have the UI and we can then test/assert the stubs properties etc.:
const stub = sinon.stub(NavigationStaticMock.prototype, "openAlertDialog");
Now lets "Act" by calling our function:
const msg = "a pointless test message";
Pointless(msg);
Finally we'll "Assert" our tests
Has the openAlertDialog()
stub been called?:
expect(stub.called).toBeTruthy();
The openAlertDialog()
stub should have only been called once:
expect(stub.calledOnce).toBeTruthy();
Was the openAlertDialog()
stubs alertStrings.text
parameter as expected?:
expect(stub.firstCall.args[0].text).toBe(msg);
Was the openAlertDialog()
stubs alertStrings.title
parameter as expected?:
expect(stub.firstCall.args[0].title).toBe("A Pointless Message");
It's quite a rudimentary test but it demonstrates the basics.
So, lets run the tests!
Execute npm run test
The output should look something like this:
> jest
PASS tests/first.test.ts (10.199 s)
sample test
Pointless
β Should display alert (34 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 10.811 s
Ran all test suites.
Fancy a crack at mocking the WebApi?
So, we've written a function that calls Xrm.WebApi, but when we try to test that function it'll fail as the Api doesn't exist... We need to mock the Api call with a stub.
A function that calls Xrm.WebApi...
First of all we'll create a function called CreateAccount
(yep you guessed it, it'll create an account!)
export async function CreateAccount(account: any): Promise<Xrm.Lookup> {
const response = await Xrm.WebApi.createRecord("account", account);
return response;
}
It's a simple example but it should get the point across.
Testing a function that calls Xrm.WebApi
So here's the test. This can be added to the first.test.ts
inside describe("sample test", () => {
It's not a great example, but it should demonstrate how we can stub the WebApi ;-)
describe("CallTheWebApi", () => {
test("Should return a valid Xrm.Lookup", async () => {
const stub = sinon
.stub(WebApiMock.prototype, "createRecord")
.withArgs("account", sinon.match.object)
.resolves({
entityType: "account",
id: "9bce6686-48d5-4d6f-85a2-da0eea30984d",
name: "Jest Account",
} as LookupValueMock);
const result: LookupValueMock = await CreateAccount({
name: "Jest Account",
creditonhold: false,
address1_latitude: 47.639583,
description: "This is the description of the sample account",
revenue: 5000000,
accountcategorycode: 1,
});
expect(stub.calledOnce).toBeTruthy();
expect(result).toBeTruthy();
expect(result.name).toBe("Jest Account");
});
});
You'll notice the absence of XrmMockGenerator.initialise();
. I moved this to the beforeEach
within describe("sample test", () => {
like so
describe("sample test", () => {
beforeEach(() => {
XrmMockGenerator.initialise();
});
...
Lets have a closer look again
First we stub the createRecord
function via the WebApiMock
object provided by xrm-mock
const stub = sinon.stub(WebApiMock.prototype, "createRecord");
Then we define the arguments for that stub (optional). Notice the use of the sinon.match
to match an object.
.withArgs("account", sinon.match.object)
NOTE If we didn't do the above the stub would be executed for all calls to Xrm.WebApi.createRecord
Finally we tell the stub what we would like it to return, or in this case as it's a async/promise we tell it what to resolve.
.resolves({
entityType: "account",
id: "9bce6686-48d5-4d6f-85a2-da0eea30984d",
name: "Jest Account",
} as LookupValueMock);
OK OK! Lets run the bloody tests!
Execute npm run test
and you should now get something similar to the following output
> jest
PASS tests/first.test.ts
sample test
Pointless
β Should display alert (7 ms)
CallTheWebApi
β Should return a valid Xrm.Lookup (2 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 2.34 s, estimated 27 s
Ran all test suites.
That's all folks!
Remember to take a look at jest, sinon, and xrm-mock
I hope that has been useful!
You can download a copy of the source code for this blog post here
In the next part we'll take a look at how we can integrate Azure Application Insights into the Webresources.
Thanks for reading.
Ollie
Top comments (0)