Guide to running and writing tests for wsdl-tsc development and for testing generated code in consumer projects.
See README for quick start and CONTRIBUTING for development setup.
The project uses three layers of testing:
- Unit tests: Pure function tests for utilities, parsers, and type mapping
- Snapshot tests: Baseline comparisons for all generated pipeline output
- Integration tests: End-to-end gateway tests using Fastify's
inject()with mock clients
All tests use Vitest and run in under 3 seconds.
npm test # All Vitest tests
npm run test:unit # Unit tests only
npm run test:snap # Snapshot tests only
npm run test:integration # Integration tests only
npm run test:conformance # WSDL capability conformance tests only
npm run test:watch # Watch mode for developmentFor the full CI pipeline including smoke tests:
npm run ciThe package supports Node.js >= 24.0.0. CI runs the complete project check on Node 24 and Node 26 so the supported floor and current Node line are both covered.
Repository test and release automation keeps disposable output classified under tmp/:
tmp/smoke/: smoke-script client, OpenAPI, gateway, app, and catalog outputtmp/cache/npm/: local npm cache used by package and dependency checkstmp/preflight/examples/: regenerated examples used by release preflighttmp/conformance/: conformance mini-projects that need repository-local module resolutiontmp/test-generation/: generated-test integration spikes
Keep new repository-owned temporary writers under the closest existing subfolder. Public CLI examples use the git-ignored .generated/ convention instead of repository tmp/ paths.
Unit tests cover pure functions with no I/O or side effects:
tools.test.ts:pascal(),resolveQName(),explodePascal(),pascalToSnakeCase(),normalizeArray(),getChildrenWithLocalName(),getFirstWithLocalName()casing.test.ts:toPathSegment()with kebab, asis, and lower stylesprimitives.test.ts:xsdToTsPrimitive()covering all XSD types (string-like, boolean, integers, decimals, floats, dates, any)errors.test.ts:WsdlCompilationErrorconstruction andtoUserMessage()formattingschema-alignment.test.ts: Cross-validates TypeScript types, JSON schemas, and catalog.json for consistencymock-data.test.ts:generateMockPrimitive(),generateMockData(),generateAllOperationMocks()with cycle detection and array wrappingwsdl-documentation.test.ts: verifies doc flow to catalogwsdlDocs, generated TS comments, OpenAPI summary and description precedence, and gateway route comment propagation
import { describe, it, expect } from "vitest";
import { pascal } from "../../src/util/tools.js";
describe("pascal", () => {
it("converts kebab-case", () => {
expect(pascal("get-weather")).toBe("GetWeather");
});
});Snapshot tests capture the complete output of the pipeline as baselines. When a generator change intentionally alters output, the snapshot diff shows exactly what changed.
- The pipeline runs against
examples/minimal/weather.wsdlinto a temp directory - Each generated file is read and compared against the stored snapshot
- A file inventory snapshot detects added or removed files
npx vitest run test/snapshot -uAlways review the diff before committing updated snapshots.
- Client output:
client.ts,types.ts,utils.ts,operations.ts,catalog.json - OpenAPI:
openapi.json - Gateway core:
plugin.ts,routes.ts,schemas.ts,runtime.ts,_typecheck.ts - Gateway routes: one handler per WSDL operation
- Gateway route comments: propagated summary and description metadata
- Gateway schemas: all model and operation JSON schema files
- File inventory: complete listing of generated files
Integration tests verify the generated gateway works end-to-end by:
- Running the pipeline in
beforeAllto generate gateway code - Dynamically importing the generated plugin
- Creating a Fastify instance with the plugin and a mock client
- Using
fastify.inject()to send HTTP requests and verify responses
The generated operations.ts provides a typed interface for creating test doubles:
import type { WeatherOperations } from "../client/operations.js";
function createMockClient(): WeatherOperations {
return {
GetCityWeatherByZIP: async (args) => ({
response: {
GetCityWeatherByZIPResult: {
Success: true,
ResponseText: "City Found",
State: "NY",
City: "New York",
Temperature: "72",
},
},
headers: {},
}),
GetCityForecastByZIP: async (args) => ({
response: {
GetCityForecastByZIPResult: {
Success: true,
ResponseText: "Forecast Found",
// Use SOAP wrapper shape — unwrapArrayWrappers() handles conversion
ForecastResult: { Forecast: [] },
},
},
headers: {},
}),
GetWeatherInformation: async (args) => ({
response: {
// Use SOAP wrapper shape — unwrapArrayWrappers() handles conversion
GetWeatherInformationResult: { WeatherDescription: [] },
},
headers: {},
}),
};
}Each method returns the same { response, headers } shape as the real SOAP client. Use the wrapper object structure matching TypeScript types — the generated unwrapArrayWrappers() function handles conversion to the flat array shape expected by JSON schemas.
import Fastify from "fastify";
const app = Fastify();
await app.register(weatherGateway, {
client: createMockClient(),
prefix: "/v1/weather",
});
await app.ready();
const res = await app.inject({
method: "POST",
url: "/v1/weather/get-city-weather-by-zip",
payload: { ZIP: "10001" },
});
expect(res.statusCode).toBe(200);
expect(res.json().status).toBe("SUCCESS");Integration tests dynamically import generated .ts files from temp directories. This works because Vitest's Vite module resolution handles TypeScript imports, JSON import attributes, and bare specifiers from the project's node_modules:
import { pathToFileURL } from "node:url";
const pluginModule = await import(pathToFileURL(join(outDir, "gateway", "plugin.ts")).href);Conformance tests live under test/conformance/ and prove WSDL capability claims through committed fixtures, registry rows, generated artifacts, diagnostics, and documentation alignment. Use the focused command when changing capability rows or fixture-backed behavior:
npm run test:conformancenpm test includes the conformance suite through normal Vitest discovery. npm run ci and release preflight cover conformance through that broad test command, while release preflight also checks that the focused command and broad CI discovery remain wired.
Examples under examples/ are consumer-facing demos and smoke-test inputs. Reusable WSDL and XSD behavior that represents a public support claim belongs in test/conformance/fixtures/ with a matching registry row.
JSON schemas flatten SOAP ArrayOf* wrapper types to plain type: "array" (when --openapi-flatten-array-wrappers is true, the default), while TypeScript types preserve the wrapper structure (e.g., ArrayOfForecast = { Forecast?: Forecast[] }).
This mismatch is resolved by the generated unwrapArrayWrappers() function in runtime.ts. Route handlers call it automatically to strip wrapper objects before Fastify serialization. Mock clients should return the real SOAP wrapper structure — the unwrap function handles the conversion.
When --openapi-flatten-array-wrappers false is used, ArrayOf* types are emitted as type: "object" and no unwrap function is generated. In this mode, mock data should use the wrapper object shape matching both the TypeScript types and the JSON schemas.
The classifyError() function puts err.message (a string) in the details field for connection and timeout errors, but the error JSON schema defines details as object | null. This causes Fastify serialization failures for 503/504 error responses. Test error classification directly via classifyError() rather than through Fastify's inject() for these error types.
The --test-dir flag generates a complete, runnable Vitest test suite that validates all generated gateway artifacts out of the box. This is the recommended way to verify generated code in consumer projects.
npx wsdl-tsc pipeline \
--wsdl-source service.wsdl \
--client-dir ./.generated/client \
--openapi-file ./.generated/openapi.json \
--gateway-dir ./.generated/gateway \
--gateway-service-name myservice \
--gateway-version-prefix v1 \
--test-dir ./.generated/tests{test-dir}/
vitest.config.ts
helpers/
mock-client.ts createMockClient() with full default responses per operation
test-app.ts createTestApp() Fastify bootstrap helper
gateway/
routes.test.ts per-operation happy path (one test per route)
errors.test.ts 400/500/502/503/504 through Fastify
envelope.test.ts SUCCESS/ERROR structure assertions
validation.test.ts invalid payloads rejected per route
runtime/
classify-error.test.ts classifyError() unit tests
envelope-builders.test.ts buildSuccessEnvelope/buildErrorEnvelope
unwrap.test.ts unwrapArrayWrappers (only when ArrayOf* wrappers exist)
npx vitest run --config ./.generated/tests/vitest.config.tsTest files that already exist are skipped by default. This allows you to customize generated tests without losing changes on regeneration. Use --force-test to overwrite all test files.
The generated helpers/mock-client.ts creates a fully typed mock client with default responses for every operation. Override individual methods for specific test scenarios:
import { createMockClient } from "./helpers/mock-client.js";
const client = createMockClient({
GetCityWeatherByZIP: async () => ({
response: { GetCityWeatherByZIPResult: { Success: false, WeatherID: 0 } },
headers: {},
}),
});Mock responses use the pre-unwrap SOAP wrapper shape. The generated unwrapArrayWrappers() function handles conversion at runtime.
For operations opted in via --stream-config, the mock returns records: AsyncIterable<RecordType> via a small asyncIterableOf helper. Generated happy-path tests assert on the configured content type; NDJSON tests parse record lines, and JSON array tests parse the response body once as an array. Override a stream op with a multi-record iterable to exercise downstream backpressure:
const client = createMockClient({
UnitDescriptiveInfoStream: async () => ({
records: (async function* () {
yield { Id: "1", Name: "Villa A" };
yield { Id: "2", Name: "Villa B" };
})(),
headers: {},
}),
});If you're using wsdl-tsc as a dependency and want to test your integration:
- Generate code with
npx wsdl-tsc pipeline --test-dir ./teststo get a ready-to-run test suite - Run
npx vitest run --config ./tests/vitest.config.tsto verify everything works - Customize generated tests as needed (they won't be overwritten on regeneration)
Alternatively, write tests manually:
- Generate code with
npx wsdl-tsc pipeline - Import the operations interface from
operations.ts - Create a mock client implementing the interface
- Register the generated gateway plugin with your mock client
- Use Fastify's
inject()to test routes without a running server
The operations interface is the recommended seam for dependency injection and testing. It's a pure TypeScript interface with no runtime dependencies on the soap package.