Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
umbraco avatar

Umbraco Msw Testing

  • 277 installs
  • 26 repo stars
  • Updated August 1, 2026
  • umbraco/umbraco-cms-backoffice-skills

Helps with testing & qa tasks.

About

umbraco-msw-testing is a Claude Code skill for testing & qa. It helps solo builders move faster with AI-assisted coding.

  • umbraco-msw-testing
  • Testing & QA
  • AI-coding skill

Umbraco Msw Testing by the numbers

  • 277 all-time installs (skills.sh)
  • +12 installs in the week ending Aug 5, 2026 (Skillselion tracking)
  • Ranked #737 of 2,153 Testing & QA skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/umbraco/umbraco-cms-backoffice-skills --skill umbraco-msw-testing

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs277
repo stars26
Last updatedAugust 1, 2026
Repositoryumbraco/umbraco-cms-backoffice-skills

What it does

Helps with testing & qa tasks.

Files

SKILL.mdMarkdownGitHub ↗

Umbraco MSW Testing

What is it?

MSW (Mock Service Worker) enables testing Umbraco backoffice extensions by intercepting API calls and returning mock responses. This is ideal for testing error states, loading states, and edge cases without a running Umbraco instance.

When to Use

  • Testing API error handling (404, 500, validation errors)
  • Testing loading spinners and skeleton states
  • Testing network retry behavior
  • Testing edge cases without backend setup
  • Adding API mocking to unit tests

Related Skills

  • umbraco-testing - Master skill for testing overview
  • umbraco-unit-testing - Unit testing patterns (combine with MSW)

Documentation

  • MSW Docs: https://mswjs.io/docs/
  • Reference handlers: Umbraco-CMS/src/Umbraco.Web.UI.Client/src/mocks/handlers/

---

Setup

Dependencies

Add to package.json:

{
  "devDependencies": {
    "@open-wc/testing": "^4.0.0",
    "@web/dev-server-esbuild": "^1.0.0",
    "@web/dev-server-import-maps": "^0.2.0",
    "@web/test-runner": "^0.18.0",
    "@web/test-runner-playwright": "^0.11.0",
    "msw": "^2.7.0"
  },
  "scripts": {
    "postinstall": "npx msw init . --save",
    "test": "web-test-runner",
    "test:watch": "web-test-runner --watch"
  }
}

Then run:

npm install
npx playwright install chromium

The postinstall script copies mockServiceWorker.js to your project root. Without this file, MSW will fail silently.

Configuration

Create web-test-runner.config.mjs:

import { esbuildPlugin } from '@web/dev-server-esbuild';
import { playwrightLauncher } from '@web/test-runner-playwright';
import { importMapsPlugin } from '@web/dev-server-import-maps';

export default {
  rootDir: '.',
  files: ['./src/**/*.test.ts', '!**/node_modules/**'],
  nodeResolve: {
    exportConditions: ['development'],
    preferBuiltins: false,
    browser: false,
  },
  browsers: [playwrightLauncher({ product: 'chromium' })],
  plugins: [
    importMapsPlugin({
      inject: {
        importMap: {
          imports: {
            '@umbraco-cms/backoffice/external/lit': '/node_modules/lit/index.js',
            '@umbraco-cms/backoffice/lit-element':
              '/node_modules/@umbraco-cms/backoffice/dist-cms/packages/core/lit-element/index.js',
            '@umbraco-cms/backoffice/element-api':
              '/node_modules/@umbraco-cms/backoffice/dist-cms/libs/element-api/index.js',
            '@umbraco-cms/backoffice/observable-api':
              '/node_modules/@umbraco-cms/backoffice/dist-cms/libs/observable-api/index.js',
            '@umbraco-cms/backoffice/context-api':
              '/node_modules/@umbraco-cms/backoffice/dist-cms/libs/context-api/index.js',
            '@umbraco-cms/backoffice/controller-api':
              '/node_modules/@umbraco-cms/backoffice/dist-cms/libs/controller-api/index.js',
            '@umbraco-cms/backoffice/class-api':
              '/node_modules/@umbraco-cms/backoffice/dist-cms/packages/core/class-api/index.js',
          },
        },
      },
    }),
    esbuildPlugin({
      ts: true,
      tsconfig: './tsconfig.json',
      target: 'auto',
      json: true,
    }),
  ],
  testRunnerHtml: (testFramework) =>
    `<html lang="en-us">
      <head>
        <meta charset="UTF-8" />
        <!-- Load MSW v2 as IIFE to get window.MockServiceWorker -->
        <script src="/node_modules/msw/lib/iife/index.js"></script>
      </head>
      <body>
        <script type="module" src="${testFramework}"></script>
      </body>
    </html>`,
};

Directory Structure

my-extension/
├── src/
│   ├── my-element.ts
│   ├── my-element.test.ts
│   └── mocks/
│       ├── handlers.ts         # MSW handlers
│       ├── setup.ts            # Worker setup
│       └── data/
│           └── items.db.ts     # Mock database
├── mockServiceWorker.js        # Generated by postinstall
├── web-test-runner.config.mjs
├── package.json
└── tsconfig.json

---

Patterns

MSW v2 Syntax

Umbraco uses MSW v2. Key API patterns:

ConceptMSW v2 Syntax
HTTP methodshttp.get(), http.post(), http.put(), http.delete()
JSON responseHttpResponse.json(data)
Status codesHttpResponse.json(data, { status: 201 })
Empty responsenew HttpResponse(null, { status: 204 })
Request params({ params }) => { ... }
Request body({ request }) => { const body = await request.json(); }
Delayawait delay(2000)

Global MSW Access

const { http, HttpResponse, delay } = window.MockServiceWorker;

umbracoPath Helper

import { umbracoPath } from '@umbraco-cms/backoffice/utils';

// Creates: /umbraco/management/api/v1/document/:id
umbracoPath('/document/:id')

Basic Handlers

GET Handler:

const { http, HttpResponse } = window.MockServiceWorker;
import { umbracoPath } from '@umbraco-cms/backoffice/utils';

export const handlers = [
  http.get(umbracoPath('/document/:id'), ({ params }) => {
    const id = params.id as string;
    return HttpResponse.json({
      id,
      name: 'Test Document',
      documentType: { alias: 'testType' },
    });
  }),
];

POST Handler:

http.post(umbracoPath('/document'), async ({ request }) => {
  const body = await request.json();

  if (!body.name) {
    return HttpResponse.json(
      {
        type: 'validation',
        status: 400,
        errors: { name: ['Name is required'] },
      },
      { status: 400 }
    );
  }

  const newId = crypto.randomUUID();
  return HttpResponse.json(
    { id: newId },
    {
      status: 201,
      headers: { 'Umb-Generated-Resource': newId },
    }
  );
}),

PUT Handler:

http.put(umbracoPath('/document/:id'), async ({ params, request }) => {
  const id = params.id as string;
  const body = await request.json();
  mockDb.update(id, body);
  return new HttpResponse(null, { status: 200 });
}),

DELETE Handler:

http.delete(umbracoPath('/document/:id'), ({ params }) => {
  const id = params.id as string;
  mockDb.delete(id);
  return new HttpResponse(null, { status: 200 });
}),

Simulating States

Error Responses:

// 404 Not Found
http.get(umbracoPath('/document/:id'), ({ params }) => {
  const doc = mockDb.read(params.id as string);
  if (!doc) return new HttpResponse(null, { status: 404 });
  return HttpResponse.json(doc);
}),

// 500 Server Error
http.get(umbracoPath('/document/:id'), () => {
  return HttpResponse.json(
    { type: 'error', detail: 'Internal server error' },
    { status: 500 }
  );
}),

Validation Errors:

http.post(umbracoPath('/document'), async ({ request }) => {
  const body = await request.json();
  if (!body.name) {
    return HttpResponse.json(
      {
        type: 'validation',
        errors: {
          name: ['Name is required'],
          title: ['Title must be at least 3 characters'],
        },
      },
      { status: 400 }
    );
  }
  return new HttpResponse(null, { status: 201 });
}),

Delayed Responses (Loading States):

http.get(umbracoPath('/slow-endpoint'), async () => {
  await delay(2000);
  return HttpResponse.json({ data: 'loaded' });
}),

Mock Database Pattern

// src/mocks/data/items.db.ts
interface Item {
  id: string;
  name: string;
  value: number;
}

class ItemsMockDb {
  private data: Item[] = [
    { id: '1', name: 'Item 1', value: 100 },
    { id: '2', name: 'Item 2', value: 200 },
  ];

  read(id: string) {
    return this.data.find((item) => item.id === id);
  }

  readAll() {
    return [...this.data];
  }

  create(item: Omit<Item, 'id'>) {
    const newItem = { ...item, id: crypto.randomUUID() };
    this.data.push(newItem);
    return newItem.id;
  }

  update(id: string, updates: Partial<Item>) {
    const index = this.data.findIndex((i) => i.id === id);
    if (index !== -1) {
      this.data[index] = { ...this.data[index], ...updates };
    }
  }

  delete(id: string) {
    this.data = this.data.filter((i) => i.id !== id);
  }
}

export const itemsDb = new ItemsMockDb();

Worker Setup

// src/mocks/setup.ts
const { setupWorker } = window.MockServiceWorker;
import { handlers } from './handlers.js';

const worker = setupWorker(...handlers);

export const startMockServiceWorker = () =>
  worker.start({
    onUnhandledRequest: 'warn',
    quiet: true,
  });

Integration with Tests

In test file:

import { expect, fixture } from '@open-wc/testing';
import { startMockServiceWorker } from './mocks/setup.js';
import './my-element.js';

// Start MSW before tests
before(async () => {
  await startMockServiceWorker();
});

describe('MyElement with API', () => {
  it('displays data from API', async () => {
    const element = await fixture(html`<my-element></my-element>`);
    await element.updateComplete;

    // Element should show mocked data
    expect(element.shadowRoot?.textContent).to.include('Item 1');
  });
});

---

Examples

Complete Handler File

// src/mocks/handlers.ts
const { http, HttpResponse } = window.MockServiceWorker;
import { umbracoPath } from '@umbraco-cms/backoffice/utils';
import { itemsDb } from './data/items.db.js';

export const handlers = [
  // List items
  http.get(umbracoPath('/my-extension/items'), () => {
    const items = itemsDb.readAll();
    return HttpResponse.json({ total: items.length, items });
  }),

  // Get single item
  http.get(umbracoPath('/my-extension/items/:id'), ({ params }) => {
    const item = itemsDb.read(params.id as string);
    if (!item) return new HttpResponse(null, { status: 404 });
    return HttpResponse.json(item);
  }),

  // Create item
  http.post(umbracoPath('/my-extension/items'), async ({ request }) => {
    const body = await request.json();
    if (!body.name) {
      return HttpResponse.json(
        { type: 'validation', errors: { name: ['Required'] } },
        { status: 400 }
      );
    }
    const id = itemsDb.create(body);
    return HttpResponse.json(
      { id },
      {
        status: 201,
        headers: { 'Umb-Generated-Resource': id },
      }
    );
  }),

  // Update item
  http.put(umbracoPath('/my-extension/items/:id'), async ({ params, request }) => {
    const id = params.id as string;
    if (!itemsDb.read(id)) return new HttpResponse(null, { status: 404 });
    itemsDb.update(id, await request.json());
    return new HttpResponse(null, { status: 200 });
  }),

  // Delete item
  http.delete(umbracoPath('/my-extension/items/:id'), ({ params }) => {
    const id = params.id as string;
    if (!itemsDb.read(id)) return new HttpResponse(null, { status: 404 });
    itemsDb.delete(id);
    return new HttpResponse(null, { status: 200 });
  }),
];

Handler Organization

src/mocks/
├── handlers.ts             # Aggregates all handlers
├── setup.ts                # Worker setup
├── handlers/
│   ├── document.handlers.ts
│   ├── media.handlers.ts
│   └── my-extension.handlers.ts
└── data/
    ├── document.db.ts
    └── items.db.ts
// handlers.ts
import { documentHandlers } from './handlers/document.handlers.js';
import { mediaHandlers } from './handlers/media.handlers.js';
import { myExtensionHandlers } from './handlers/my-extension.handlers.js';

export const handlers = [
  ...documentHandlers,
  ...mediaHandlers,
  ...myExtensionHandlers,
];

---

Running Tests

# Run all tests
npm test

# Run in watch mode
npm run test:watch

# Run specific file
npx web-test-runner src/my-element.test.ts

---

Troubleshooting

MSW not intercepting requests

1. Check mockServiceWorker.js exists in project root 2. Verify MSW script is loaded in test HTML: <script src="/node_modules/msw/lib/iife/index.js"></script> 3. Ensure worker is started before tests run

"http is not defined"

Use global access: const { http, HttpResponse } = window.MockServiceWorker;

Handler not matching

Check path matches exactly. Use umbracoPath() for Umbraco API paths.

Requests still hitting real server

Ensure onUnhandledRequest: 'warn' is set to see unhandled requests in console.

---

Migration from MSW v1

If upgrading from MSW v1, here are the key changes:

MSW v1MSW v2
rest.get()http.get()
rest.post()http.post()
(req, res, ctx) => res(ctx.json(data))() => HttpResponse.json(data)
res(ctx.status(404))new HttpResponse(null, { status: 404 })
res(ctx.delay(2000), ctx.json(data))await delay(2000); return HttpResponse.json(data)
req.params.id({ params }) => params.id
await req.json()({ request }) => await request.json()

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.