Skip to content

test: SanityCommand unit testing approach#1335

Merged
filmaj merged 6 commits into
mainfrom
cmd-unit-testing
Jun 23, 2026
Merged

test: SanityCommand unit testing approach#1335
filmaj merged 6 commits into
mainfrom
cmd-unit-testing

Conversation

@filmaj

@filmaj filmaj commented Jun 19, 2026

Copy link
Copy Markdown
Member

Description

Draft / Proof-of-concept on how to unit test SanityCommand classes. WIP. Addresses RUN-1420.

What to review

This PR is meant as a conversation starting point. Unit testing OCLIF commands can be tricky given their object-oriented nature; testing class instance side effects can be hard, especially when using inheritance (as OCLIF recommends via its custom base class guide). OCLIF's recommended testing approach, IMO, encourages people down an integration-test heavy approach as its native test tooling shells out to a process and captures output streams in order to assert on behaviour.

The changes in this PR implement a mock-based approach: it exposes a MockSanityCommand class, along with a factory, that installs mocks for SanityCommand APIs, and returns them along with the mocked class. Then it is up to each unit test implementor to install the mocked SanityCommand class in place of the real SanityCommand class. At this point, a unit test file can exercise a SanityCommand fully in memory using standard vitest (or any other test runner) mocking tools. This helps us keep tests fast and thus iteration cycles short.

One benefit to this approach is that we can test things like flag combinations without needing to touch the filesystem. Many of our slowest tests require complicated test fixtures (studio projects) in order to validate superficial and simple logical branches like flag validation.

One downside to this approach, typical for any testing approach employing mocks, is that as the interfaces for a module evolve, so must the mocks that pair with them. However, I don't think this is a major problem in practice, as the SanityCommand class changes infrequently (the last change was 3 months ago).

Testing

This PR attempts to use two commands to prove its viability: the schema extract and doctor commands:

  1. The schema command is a very thin wrapper that delegates to two other modules. Unit testing this one is dead simple: validate that the command delegates to the appropriate other modules. As long as we ensure the modules delegated to also have unit test coverage, we then have a lot of confidence about preventing regressions in this code.
  2. The doctor command is a bit more complicated and embeds a lot of logic directly within the OCLIF command methods - these sorts of implementations are now much more easily testable in-memory using the framework proposed in this PR.

Fixes RUN-1420.


Note

Low Risk
Test infrastructure and visibility tweaks on the CLI base class; doctor behavior should be equivalent aside from routing output through the same bound helpers.

Overview
Introduces a mock-based pattern for fast in-memory OCLIF command tests: SanityCommandInterface, a public surface on SanityCommand (output, getCliConfig, getProjectId, getProjectRoot, isUnattended, resolveIsInteractive), and createMockSanityCommand() so tests can swap in a MockedSanityCommand via vi.mock('@sanity/cli-core').

doctor now routes console I/O through this.output instead of this.log / this.error, with unit tests covering human output and --json. schema extract gets similar unit tests (default vs --watch, invalid flags). The integration doctor suite drops the redundant --json case now covered by units.

Reviewed by Cursor Bugbot for commit 4a695f0. Bugbot is set up for automated code reviews on this repo. Configure here.

@filmaj filmaj self-assigned this Jun 19, 2026
@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Stats — @sanity/cli

Compared against main (944c6ef4)

@sanity/cli

Metric Value vs main (944c6ef)
Internal (raw) 2.7 KB -
Internal (gzip) 1.0 KB -
Bundled (raw) 11.16 MB -
Bundled (gzip) 2.10 MB -
Import time 878ms +11ms, +1.2%

bin:sanity

Metric Value vs main (944c6ef)
Internal (raw) 782 B -
Internal (gzip) 423 B -
Bundled (raw) 9.87 MB -
Bundled (gzip) 1.78 MB -
Import time 2.02s +44ms, +2.2%

🗺️ View treemap · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

📦 Bundle Stats — @sanity/cli-core

Compared against main (944c6ef4)

Metric Value vs main (944c6ef)
Internal (raw) 106.7 KB -
Internal (gzip) 26.7 KB -
Bundled (raw) 21.72 MB -
Bundled (gzip) 3.46 MB -
Import time 779ms -4ms, -0.6%

🗺️ View treemap · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

📦 Bundle Stats — create-sanity

Compared against main (944c6ef4)

Metric Value vs main (944c6ef)
Internal (raw) 908 B -
Internal (gzip) 483 B -
Bundled (raw) 931 B -
Bundled (gzip) 491 B -
Import time ❌ ChildProcess denied: node -
Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Coverage Delta

File Statements
packages/@sanity/cli-core/src/SanityCommand.ts 36.0% (±0%)
packages/@sanity/cli/src/commands/doctor.ts 96.1% (±0%)

Comparing 2 changed files against main @ 944c6ef44cc84e12f0c617c2177c58d60afdd74e

Overall Coverage

Metric Coverage
Statements 53.1% (- 1.7%)
Branches 42.8% (- 1.4%)
Functions 51.4% (- 2.8%)
Lines 53.8% (- 1.7%)

@filmaj filmaj marked this pull request as ready for review June 19, 2026 19:10
@filmaj filmaj requested a review from a team as a code owner June 19, 2026 19:10

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1c9f7cc. Configure here.

Comment thread packages/@sanity/cli/test/mockSanityCommand.ts
public async init(): Promise<void> {
const {args, flags} = await this.parse({
args: this.ctor.args,
baseFlags: super.ctor.baseFlags,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mock init wrong baseFlags

Low Severity

MockedSanityCommand.init passes super.ctor.baseFlags into parse, while SanityCommand.init uses (super.ctor as typeof SanityCommand).baseFlags. In-memory command tests can parse a different flag set than production once shared base flags exist on SanityCommand.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1c9f7cc. Configure here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The difference pointed out here is purely type-specific, should not affect runtime.

@filmaj filmaj force-pushed the cmd-unit-testing branch from 1c9f7cc to 78c98e7 Compare June 22, 2026 17:40
@filmaj filmaj requested a review from snocorp June 22, 2026 20:01
snocorp
snocorp previously approved these changes Jun 23, 2026
@filmaj filmaj force-pushed the cmd-unit-testing branch from a970b8d to 4a695f0 Compare June 23, 2026 15:41
@filmaj filmaj enabled auto-merge (squash) June 23, 2026 15:49
@filmaj filmaj merged commit 2bad755 into main Jun 23, 2026
54 checks passed
@filmaj filmaj deleted the cmd-unit-testing branch June 23, 2026 15:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants