feat: Test generation for modular SDK clients#3809
Conversation
Co-authored-by: MaryGao <9943211+MaryGao@users.noreply.github.com>
|
@copilot Run |
Co-authored-by: v-jiaodi <80496810+v-jiaodi@users.noreply.github.com>
Done in f271277. Ran |
|
@copilot one issue found in our CI
|
…ilures Co-authored-by: v-jiaodi <80496810+v-jiaodi@users.noreply.github.com>
Fixed in f513619. The root cause was that
All tests now pass: 152 test-next, 860 unit tests, 559 RLC integration, 576 modular integration. |
|
@copilot pls fix format issue |
Co-authored-by: MaryGao <9943211+MaryGao@users.noreply.github.com>
|
@copilot please update the PR description with design proposal |
Here is the design proposal for the PR description: Design Proposal: Test Generation for Modular SDK ClientsOverviewThis feature adds automatic ArchitectureNew Files
Modified Files
Generation FlowGenerated Test StructureEach generated test file follows this pattern: describe("<operation description>", () => {
let recorder: Recorder;
let client: MyClient;
beforeEach(async function(ctx) {
recorder = await createRecorder(ctx); // from static/test-helpers/
const subscriptionId = env["SUBSCRIPTION_ID"] || "<default>";
const credential = createTestCredential(); // from @azure-tools/test-credential
const clientOptions = recorder.configureClientOptions({});
client = new MyClient(credential, subscriptionId, clientOptions);
});
afterEach(async function() {
await recorder.stop();
});
it("should <example name>", async function() {
const result = await client.resourceGroup.get(...);
assert.ok(result);
assert.equal(result.name, "expectedName"); // from example response body
});
});Key Design Decisions
|
There was a problem hiding this comment.
Pull request overview
Adds first-class test generation support for Modular SDK clients in the TypeSpec TypeScript emitter, including new emitter logic, shared example-value utilities, and the static helper assets required for recorded tests.
Changes:
- Introduces a new
emitTestspipeline for Modular generation, driven by TypeSpec examples. - Adds example-value serialization/assertion helpers and new modular unit-test scenarios for test generation.
- Extends static-helper loading/binding to support loading test helpers into
test/generated/utiland new test-related external dependencies.
Reviewed changes
Copilot reviewed 26 out of 28 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/typespec-ts/src/modular/helpers/exampleValueHelpers.ts | Adds shared example-value serialization + request building + response assertion generation for tests. |
| packages/typespec-ts/src/modular/emitTests.ts | New Modular test emitter that creates test/generated/*.spec.ts based on examples. |
| packages/typespec-ts/static/test-helpers/recordedClient.ts | Adds createRecorder helper used by generated tests. |
| packages/typespec-ts/src/modular/external-dependencies.ts | Adds AzureTestDependencies for vitest + test-recorder/test-credential imports. |
| packages/typespec-ts/src/modular/static-helpers-metadata.ts | Registers CreateRecorderHelpers.createRecorder static helper metadata. |
| packages/typespec-ts/src/framework/load-static-helpers.ts | Adds support for loading static/test-helpers into test/generated/util (via loadTestHelpers/generateTest). |
| packages/typespec-ts/src/framework/hooks/binder.ts | Updates binder to optionally clean test helper outputs and adjusts unused-helper cleanup logic. |
| packages/typespec-ts/src/index.ts | Wires test generation into $onEmit and adds test dependencies/static helpers. |
| packages/typespec-ts/test/util/testUtil.ts | Updates test harness to load test helpers and test dependencies in binder setup. |
| packages/typespec-ts/test/util/emitUtil.ts | Adds emitTestsFromTypeSpec helper for modular unit scenarios. |
| packages/typespec-ts/test/modularUnit/scenarios.spec.ts | Adds markdown code-block support for `(ts |
| packages/typespec-ts/test/modularUnit/scenarios/test/**.md | Adds new modular unit scenarios validating generated tests across operations/params/responses. |
| packages/typespec-test/test/openai_modular/generated/typespec-ts/src/static-helpers/serialization/get-binary-response-browser.mts | Updates generated output (file removed). |
| packages/typespec-test/test/batch_modular/generated/typespec-ts/src/static-helpers/serialization/get-binary-response-browser.mts | Updates generated output (file removed). |
packages/typespec-ts/test/modularUnit/scenarios/test/operations/dpgOperationsTest.md
Outdated
Show resolved
Hide resolved
packages/typespec-ts/test/modularUnit/scenarios/test/operations/clientParameterTest.md
Outdated
Show resolved
Hide resolved
packages/typespec-ts/src/modular/helpers/exampleValueHelpers.ts
Outdated
Show resolved
Hide resolved
…s/dpgOperationsTest.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…s/clientParameterTest.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…ts for multi-client support Co-authored-by: v-jiaodi <80496810+v-jiaodi@users.noreply.github.com>
|
@copilot Run |
…lper variants Co-authored-by: v-jiaodi <80496810+v-jiaodi@users.noreply.github.com>
…nder.ts (reverts 34c79d6) Co-authored-by: v-jiaodi <80496810+v-jiaodi@users.noreply.github.com>
| @@ -1,20 +0,0 @@ | |||
| import { HttpResponse, StreamableMethod } from "@azure-rest/core-client"; | |||
There was a problem hiding this comment.
why these two files are not loaded?
There was a problem hiding this comment.
This is un-expected and I will take a look.
packages/typespec-ts/test/modularUnit/scenarios/test/operations/clientParameterTest.md
Show resolved
Hide resolved
packages/typespec-ts/test/modularUnit/scenarios/test/operations/lroOperationTest.md
Show resolved
Hide resolved
| if (option.azureSdkForJs && option.generateTest === undefined) { | ||
| if (hasTestFolder) { | ||
| option.generateTest = false; | ||
| } else { | ||
| option.generateTest = true; | ||
| } | ||
| } |
There was a problem hiding this comment.
Not related to this PR, but seems this block can be removed because option.generateTest is always a boolean after enrichDpgContext is called.
| // Enable modular test generation when generateTest is true | ||
| if ( | ||
| dpgContext.rlcOptions?.generateTest && | ||
| isAzurePackage({ options: rlcOptions }) |
There was a problem hiding this comment.
Is it safe to remove isAzurePackage({ options: rlcOptions })?
There was a problem hiding this comment.
sure, let's double check if needed.
There was a problem hiding this comment.
@v-jiaodi could you enable one smoke test for test generation?
| switch (value.type.kind) { | ||
| case "utcDateTime": | ||
| assertions.push( | ||
| `assert.strictEqual(${path}, new Date("${value.value}"));` |
There was a problem hiding this comment.
Will this always fail?
I just tested the code below and it turns false:
const createdAt = new Date("2020-05-05T17:18:19.1234567Z")
console.log(createdAt === new Date("2020-05-05T17:18:19.1234567Z"))
|
Is it possible to enable one smoke test (e.g. NetworkAnalytics.Management) to be generated with tests? |



Here is the design proposal for the PR description:
Design Proposal: Test Generation for Modular SDK Clients
Overview
This feature adds automatic
.spec.tstest file generation for modular SDK clients from TypeSpec examples (x-ms-examples). It mirrors the existing samples generation infrastructure but targets recorded integration tests using@azure-tools/test-recorder.Architecture
New Files
src/modular/emitTests.tssrc/modular/helpers/exampleValueHelpers.tsstatic/test-helpers/recordedClient.tscreateRecorder()copied totest/generated/util/Modified Files
src/modular/external-dependencies.tsAzureTestDependencies(Recorder, createTestCredential, assert, vitest hooks, env)src/modular/static-helpers-metadata.tsCreateRecorderHelpers.createRecorderrefkeysrc/framework/load-static-helpers.tsstatic/test-helpers/→test/generated/util/whenloadTestHelpersis setsrc/framework/hooks/binder.tsgetImportDeclarations()to avoid name conflictssrc/index.tsemitTests()whengenerateTest && isAzurePackageGeneration Flow
Generated Test Structure
Each generated test file follows this pattern:
Key Design Decisions
Conditional loading: Test helpers (
static/test-helpers/) are only copied whengenerateTest && isAzurePackage(production) orloadTestHelpers: true(unit tests). This avoids breaking theload-static-filesintegration test.Shared utilities:
exampleValueHelpers.tsis shared between samples and tests. The key difference is that tests do NOT convertplainDatestrings toDateobjects — they stay as strings.Credential handling: For ARM/Azure services, tests use
createTestCredential()from@azure-tools/test-credentialto support both live and playback modes. Non-Azure services fall back to stub values.Response assertions: Simple scalar fields from the example response body generate
assert.equal()statements. Complex/nested objects are skipped to keep tests maintainable.Binder import conflict resolution: The binder's
generateLocallyUniqueImportNamechecks both tracked imports andsourceFile.getImportDeclarations()to handle the manualaddImportDeclarationcall for the client class.