Skip to content

Extending Mockifyr

Mockifyr is a library as well as a server. When a stub cannot express what you need, you register your own code against one of the extension seams.

Registration

Everything is wired through AddMockifyr(cfg => …), whose argument is a MockifyrExtensions builder.

builder.Services.AddMockifyr(cfg => cfg
.AddMatcher("tenant-quota", new TenantQuotaMatcher())
.AddTemplateHelper("shout", args => args[0]?.ToString()?.ToUpperInvariant() ?? "")
.AddServeEventListener(new AuditListener())
.AddResponseTransformer(new SignatureTransformer())
.AddAdminApiExtension(new QuotaAdminApi()));
Builder methodRegisters
AddMatcher(name, matcher)A named custom request matcher
AddServeEventListener(listener)A listener fired after a request is served
AddTemplateHelper(name, render)A Handlebars helper usable as {{name …}}
AddAdminApiExtension(extension)Custom endpoints under /__admin/ext/…
AddResponseTransformer(transformer)A transformer applied to the rendered response

Why the seams look like this

The core engine has zero external dependencies, does no I/O, and never references a transport. Delay, fault and proxy are not things the engine performs — they are directives the engine emits and the facade carries out, and every outbound call leaves through IServeEventListener.

That is the reason the seams split the way they do: a matcher or a transformer is a pure function of its input, while anything that talks to the network is a listener. If your extension needs to make an HTTP call, it belongs on the listener seam, not inside a matcher.

Custom matchers

A matcher registered with AddMatcher is reached from stub JSON by name:

{
"request": {
"method": "GET",
"urlPath": "/reports",
"customMatcher": { "name": "tenant-quota", "parameters": { "max": 100 } }
},
"response": { "status": 429 }
}

The built-in graphql-body-matcher is registered the same way, and is a working example of the shape.

Admin API extensions

IAdminApiExtension exposes a RoutePrefix. Requests to /__admin/ext/{prefix}/… are routed to the extension whose prefix equals the first path segment under /__admin/ext/. An unknown prefix is a 404.

Terminal window
curl http://localhost:8080/__admin/ext/quota/status

These routes are host-level, not tenant-scoped — see multi-tenancy.

The public seams

All declared in Mockifyr.Core.

InterfacePurpose
IResponseTransformerTransform a rendered response
IResponseDefinitionTransformerTransform a response definition before rendering
ITemplateHelperA named template helper
ITemplateHelperProviderSupplies a set of helpers
ITemplateModelProviderAdds data to the template model
IMatcherRegistryRegister and resolve named matchers
IAdminApiExtensionCustom /__admin/ext/… endpoints
IMappingsLoaderLoad stub mappings from a custom source
IStubPersistencePersist stubs to a custom backing store
IEnvironmentPersistencePersist environment keys

Not wired yet

Present in the public surface but not currently invoked. Implementing them has no effect, so do not build against them:

  • IResponseDefinitionTransformer
  • ITemplateModelProvider
  • IRequestFilter
  • Template-helper hash arguments ({{helper key=value}}) and helper providers (ITemplateHelperProvider) — a helper registered through AddTemplateHelper receives positional arguments only.

Registered through DI, not the builder

There is no builder method for IMappingsLoader or IStubPersistence. Register those as ordinary dependency-injection services; the host resolves them from the container.

In the dashboard

The dashboard has an Extensions page. It is a static, searchable reference of the built-in capabilities — matchers, helpers, transformers — not an editor. Extensions are registered in code at startup; nothing on that page changes what the host has loaded.