Skip to main content
umbraco-msw-testing MSW (Mock Service Worker) patterns for testing Umbraco backoffice extensions with mocked APIs
Ir para a instalação Skills Marketplace Descubra e explore skills de IA criadas pela comunidade.
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Copiar promptMostrar detalhes do prompt Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
npx skills add https://github.com/umbraco/Umbraco-CMS-Backoffice-Skills --skill umbraco-msw-testingO comando permanece em uma só linha. Role horizontalmente para revisá-lo antes de copiar.
Prefere uma cópia local? Baixe os arquivos disponíveis atualmente no SkillsMP.
Baixar Zip Baixando... Explorador de arquivos
12 arquivos Ocupações relacionadas
SOC
Baseado na classificação ocupacional SOC
name umbraco-msw-testing description MSW (Mock Service Worker) patterns for testing Umbraco backoffice extensions with mocked APIs version 2.0.0 location managed allowed-tools Read, Write, Edit, WebFetch
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 {
"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"
}
}
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:
Concept MSW v2 Syntax HTTP methods http.get(), http.post(), http.put(), http.delete()JSON response HttpResponse.json(data)Status codes HttpResponse.json(data, { status: 201 })Empty response new HttpResponse(null, { status: 204 })Request params ({ params }) => { ... }Request body ({ request }) => { const body = await request.json(); }Delay await delay(2000)
Global MSW Access const { http, HttpResponse , delay } = window .MockServiceWorker ;
umbracoPath Helper import { umbracoPath } from '@umbraco-cms/backoffice/utils' ;
umbracoPath ('/document/:id' )
Basic Handlers 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' },
});
}),
];
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 },
}
);
}),
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 });
}),
http.delete (umbracoPath ('/document/:id' ), ({ params } ) => {
const id = params.id as string ;
mockDb.delete (id);
return new HttpResponse (null , { status : 200 });
}),
Simulating States
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);
}),
http.get (umbracoPath ('/document/:id' ), () => {
return HttpResponse .json (
{ type : 'error' , detail : 'Internal server error' },
{ status : 500 }
);
}),
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
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
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 import { expect, fixture } from '@open-wc/testing' ;
import { startMockServiceWorker } from './mocks/setup.js' ;
import './my-element.js' ;
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 ;
expect (element.shadowRoot ?.textContent ).to .include ('Item 1' );
});
});
Examples
Complete Handler File
const { http, HttpResponse } = window .MockServiceWorker ;
import { umbracoPath } from '@umbraco-cms/backoffice/utils' ;
import { itemsDb } from './data/items.db.js' ;
export const handlers = [
http.get (umbracoPath ('/my-extension/items' ), () => {
const items = itemsDb.readAll ();
return HttpResponse .json ({ total : items.length , items });
}),
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);
}),
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 },
}
);
}),
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 });
}),
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
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
npm test
npm run test :watch
npx web-test-runner src/my-element.test.ts
Troubleshooting
MSW not intercepting requests
Check mockServiceWorker.js exists in project root
Verify MSW script is loaded in test HTML: <script src="/node_modules/msw/lib/iife/index.js"></script>
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 v1 MSW 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.idawait req.json()({ request }) => await request.json()