npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@xrmforge/testing

v0.8.0

Published

Type-safe testing utilities for Dynamics 365 form scripts

Downloads

414

Readme

@xrmforge/testing

npm version license

Type-safe testing utilities for Dynamics 365 form scripts. Builds mock FormContext objects from your generated form interfaces, with compile-time field validation -- no more as any, no more hand-wired XrmMockGenerator setup.

Part of XrmForge. Works with any test runner; examples use Vitest.


Installation

npm install --save-dev @xrmforge/testing

Requirements: @types/xrm (>= 9.0.0) as a peer dependency.


createFormMock: a typed mock FormContext

Pass your generated form interface as the type parameter. The initial values are validated against the form's fields at compile time, so a typo or a wrong value type is a build error, not a silent test pass.

import { describe, it, expect } from 'vitest';
import { createFormMock } from '@xrmforge/testing';
import type { AccountMainForm, AccountMainFormMockValues as MockValues } from '../../generated/forms/account.js';
import { onLoad } from '../../src/forms/account-form.js';

describe('Account onLoad', () => {
  it('locks the MPK field when set', () => {
    const mock = createFormMock<AccountMainForm>({
      markant_ismpk: 1,         // compile error if the field or type is wrong
    } satisfies MockValues);

    onLoad(mock.asEventContext());

    expect(mock.getControl('markant_ismpk').getDisabled()).toBe(true);
  });
});

createFormMock(values, options?) accepts:

  • values -- field values as a plain object (lazily creates absent fields as null).
  • options.entityName / options.entityId -- seed the entity (default: 'unknown' / null GUID).
  • options.formType -- the value returned by ui.getFormType() (default 2 = Update; set 1 to test create-only paths).
  • options.tabs -- seed a tab/section structure so ui.tabs.get(), forEach, and section visibility become testable.

The FormMock API

The returned FormMock<TForm> gives you the typed context plus test-friendly accessors:

| Member | Purpose | |--------|---------| | formContext | The mock FormContext, typed as your form interface. | | getValue(name) / setValue(name, v) | Shorthand for getAttribute(name).getValue()/.setValue(). | | getAttribute(name) | The underlying MockAttribute for detailed assertions. | | getControl(name) | The underlying MockControl (visibility, disabled, etc.). | | ui | The MockUi for form-notification assertions. | | asEventContext() | An Xrm.Events.EventContext for onLoad handlers. | | asAttributeEventContext(field) | An event context with the field as event source (for onChange). | | fireOnChange(field) | Fire all registered onChange handlers for a field. | | fireOnSave(saveMode?) | Fire onSave handlers; returns true if a handler called preventDefault(). |

Testing onChange and onSave

const mock = createFormMock<ContactMainForm>({ statuscode: 1 });

mock.getAttribute('statuscode').setValue(2);
mock.fireOnChange('statuscode');          // runs your registered onChange logic

const cancelled = mock.fireOnSave(70);    // 70 = AutoSave
expect(cancelled).toBe(false);

setupXrmMock: a global Xrm for free-standing code

When your code calls Xrm.WebApi, Xrm.Navigation, Xrm.Utility, or Xrm.App outside a form context, install a minimal global Xrm:

import { beforeEach, afterEach } from 'vitest';
import { setupXrmMock, teardownXrmMock } from '@xrmforge/testing';

beforeEach(() => setupXrmMock());
afterEach(() => teardownXrmMock());

Override only the methods a test needs; everything else returns a safe default:

setupXrmMock({
  webApiOverrides: {
    retrieveMultipleRecords: async () => ({ entities: [{ name: 'Test' }] }),
  },
  globalContextOverrides: { languageId: 1031, userName: 'Test User' },
});

Override groups: webApiOverrides, navigationOverrides, utilityOverrides, appOverrides, and globalContextOverrides (client URL, language, user, security roles). Methods are plain functions, not spies -- wrap them in vi.fn() yourself if you need call assertions.

Xrm.App notifications are tracked: the default addGlobalNotification assigns a unique id per call and records the notification, clearGlobalNotification removes it by id, and Xrm.App.getGlobalNotifications() returns the active ones -- so polling / cloud-flow tests can assert on banners without stubbing Xrm.App themselves:

setupXrmMock();
const id = await (globalThis as any).Xrm.App.addGlobalNotification({ message: 'Polling...' });
// ... code under test runs, then finishes and clears the banner ...
expect((globalThis as any).Xrm.App.getGlobalNotifications()).toHaveLength(0);

For an HTML WebResource / IFrame control, inject the content window your code expects with control.setContentWindow({ setClientApiContext, /* ... */ }); getContentWindow() returns it (default: an empty window).


Exports

createFormMock, setupXrmMock, teardownXrmMock, the mock classes MockAttribute, MockControl, MockEntity, MockUi, MockEventContext, and the types FormMock, CreateFormMockOptions, MockTabConfig, MockSectionConfig, FormNotification, SetupXrmMockOptions, TrackedAppNotification.

Documentation

Full guide: XrmForge on GitHub.

License

MIT (c) XrmForge Contributors.