Showing posts with label Modules. Show all posts
Showing posts with label Modules. Show all posts

Unit test Web API calls with xrm-mock and SinonJS

Setting the scene

We write JavaScript to perform business logic client-side on Dynamics forms.

  • Sometimes we want to dynamically alter the layout of a form (collapse tabs, hide sections etc.)
  • Sometimes we want to update field values
  • And sometimes we want to retrieve data from Dynamics using the Web API and then act on it

No matter why we're writing client-side scripts, we always want to write tests for them.

I've previously written in this blog post and others how basic Xrm functions can be tested against using xrm-mock. However, what about more advanced Xrm functions such as Xrm.WebApi?

Introducing the Web API with v9

Xrm.WebApi was introduced with Dynamics 365 version 9. In Microsoft's words, it: "Provides properties and methods to use Web API to create and manage records and execute Web API actions and functions in Customer Engagement".

My interpretation would be that it enables developers to interact with the Web API using shorthand code. For example, prior to version 9, one would write the following to create an account:

Now, using Xrm.WebApi this can be rewritten as:

Faking Web API calls using xrm-mock

XrmMockGenerator.initialise() initialises an empty Xrm.WebAPI object. Calls to its methods such as createRecord throw a not implemented error. The current recommended approach is to therefore stub any API methods being called in the code under test, and force their return values. This allows you to:

  • control your test's expected behaviour
  • prevent direct calls to the Dynamics database via XMLHttpRequest or similar

Here's an example

This example demonstrates a basic client-side script running on a Contact form in Dynamics. When the form is loaded, the script:

  • gets the Id of the Contact's Parent Contact via Xrm.Page.getAttribute.getValue
  • retrieves the Parent Contact's name via Xrm.WebApi.retrieveRecord
  • sets the Contact's description to "My parent is called {parent contact's name}" via Xrm.Page.getAttribute.setValue

This example uses Sinon.JS to help create Web Api stubs.

"Standalone and test framework agnostic JavaScript test spies, stubs and mocks (pronounced "sigh-non", named after Sinon, the warrior)."

First, here's contact.ts, the script which will be run on the Contact form:

And here's contact.test.ts, the script we'll use to test contact.ts:

Walkthrough: Understand the Code

Testing in action with Wallaby.js

And that's it

Using the example above you can see how to:

  • Create a fake Xrm object to use in client-side unit tests by using xrm-mock.
  • Stub a call to Xrm.WebApi to control your test's behaviour and not directly call the Dynamics database.
  • Write asynchronous TypeScript code for Dynamics.

This example and all its source code is available on xrm-mock's GitHub page here.

A case for modular JavaScript development in Dynamics 365

JavaScript files become large, very quickly, when it comes to customising Dynamics 365 forms. Code reusability tends to be low across forms, and external libraries often have vast amounts of redundant code.

The module pattern in JavaScript is similar to classes (if you're familiar with C#, Java, Python etc.). Each module is self-contained with distinct functionality, and can be decoupled from other chunks of code with minimal fuss. In Dynamics 365, a module might represent:

  • logic to support toggling sections on a form
  • parts of a framework to support Web API calls
  • reusable security role querying and validation helpers
  • common numeric functions such as VAT calculation

The benefits should be becoming apparent at this point. We've touched on maintainability and reusability, but there's also testability. Each module can have its own individual unit tests, and each form (i.e. contact, account) can be tested end to end. See my GitHub page for an example.

How do I write modular JavaScript for Dynamics?

In this example, I'll demonstrate a requirement that locks every field on the contact form when it is loaded, if the user does not have a specific security role.

We need three modules: contact, security and fields. Security needs to get the users roles and determine if a given security role exists within the users roles:

Note: certain syntax will be unfamiliar here, if you're new to modules. The contents of the functions should feel familiar though.

Fields needs to be able to lock all fields on a form:

Notice the line module.exports = new(); This exposes the module to other modules, which can make use of it by using the require syntax, as shown in the final form contact:

And you're done! The contact form pulls in functions from other reusable files and makes use of them to complete some business logic end-to-end inside of its onLoad function.

But wait... the file path "../security.js" might mean something to your code editor, but it means nothing inside a browser. Chrome or IE therefore won't be able to load the files required by the contact form.

You're going to need a bundler

Bundlers recursively check your application's dependencies and package the modules needed into one (or more) browser-safe bundle.

In this example, I use my preferred bundler, Webpack.

Start by ensuring you have Node.js installed. Then, open a command window in your script's project directory e.g. C:\source\repos\crm-project\web-resources.

To install the latest release of Webpack locally, run: npm install --save-dev webpack

You're now going to need a script to build your contact module. Open your package.json file (which should exist having installed Webpack). Add the following script:

Replace ./src/contact/.. with the relative path of your contact.js file.

Running this script packages your contact script and all of its dependencies and outputs them to the file ./dist/contact.bundle.min.js. The additional tag of --optimize-minimize minimises your script, so that it's smaller in size and production-ready. The script can be run from your command line using npm run build-contact

Now you're (really) done!
Some notes and caveats:
  • Using JavaScript to lock fields often isn't the best approach because it's client-side and can therefore be overriden.
  • Minimised scripts aren't readable or easily debugged. You may wish to exclude --optimize-minimize if you're deploying to a development or sandbox environment.
  • I've chosen one of several ways to implement JavaScript modules. There are pros and cons to each. For detail see here.