Skip to content
Skills Library
Programming · Testing

write-tests

/write-tests

Write tests for Syncpack using the TestBuilder pattern. Use when adding tests for commands, validation logic, or any new functionality. Covers TestBuilder API, assertion patterns, and common test scenarios.

Repository
syncpack ↗
Licence
MIT
Last repository change
09.08.2026

Assessment and security

100/ 100Class: good
Format25 / 25
Frontmatter with name and description, valid name, description up to 1,024 characters, name matching the folder.
Discoverability25 / 25
Description explains when to use the skill, has a reasonable length and uses third person.
Content35 / 35
At least 150 words and at most 500 lines, headings, step lists, examples or code.
Hygiene15 / 15
No placeholders such as TODO, no Windows paths, with a licence.
Riskno signals detected in the scan
Files in this package
2
Scripts
0
HTML comments
0
URLs in SKILL.md
0
Deprecation

No signals detected. Repository changed on 09.08.2026, without references to retired models or APIs.

Licence

MIT, open: use and modify while retaining author attribution.

What it does and when to use it

Write tests for Syncpack using the TestBuilder pattern. Use when adding tests for commands, validation logic, or any new functionality. Covers TestBuilder API, assertion patterns, and common test scenarios.

Original author description

Write tests for Syncpack using the TestBuilder pattern. Use when adding tests for commands, validation logic, or any new functionality. Covers TestBuilder API, assertion patterns, and common test scenarios.

How it works

  1. Golden rules
  2. TDD
  3. Quick start
  4. Sub-module organisation
  5. File location
  6. Builder methods
  7. ExpectedInstance fields
  8. Patterns
  9. Fix tests
  10. Common mistakes
Original SKILL.md headings · 180 lines, 738 words
SKILL.md preview
---
name: write-tests
description: Write tests for Syncpack using the TestBuilder pattern. Use when adding tests for commands, validation logic, or any new functionality. Covers TestBuilder API, assertion patterns, and common test scenarios.
---

# Write Tests

## Golden rules

- Use `TestBuilder` — never construct `Context` manually.
- Assert with `expect(&ctx).to_have_instances(vec![...])` — never index `ctx.instances`.
- Async-first: `#[tokio::test]` + `.run().await` is the canonical entry.

## TDD

1. Read 2-3 tests in the same `*_test.rs` and copy the pattern
2. Write failing test → `just test` → confirm RED
3. Ask before implementing
4. Implement minimal code → GREEN → `just format`

## Quick start

```rust
use {
  crate::{
    instance::{FixableInstance::*, InstanceState, SuspectInstance::*, UnfixableInstance::*, ValidInstance::*},
    test::{
      builder::TestBuilder,
      expect::{expect, ExpectedInstance},
    },
  },
  serde_json::json,
};

#[tokio::test]
async fn pinned_version_replaces_anything_different() {
  let ctx = TestBuilder::new()
    .with_package(json!({
      "name": "package-a",
      "version": "1.0.0",
      "devDependencies": {"foo": "workspace:*"}
    }))
    .with_version_group(json!({
      "dependencies": ["foo"],
      "pinVersion": "1.2.0"
    }))
    .run()
    .await;
  expect(&ctx).to_have_instances(vec![
    ExpectedInstance {
      state: InstanceState::valid(IsLocalAndValid),
      dependency_name: "package-a",
      id: "package-a in /version of package-a",
      actual: "1.0.0",
      expected: Some("1.0.0"),
      overridden: None,
    },
    ExpectedInstance {
      state: InstanceState::fixable(DiffersToPin),
      dependency_name: "foo",
      id: "foo in /devDependencies of package-a",
      actual: "workspace:*",
      expected: Some("1.2.0"),
      overridden: None,
    },
  ]);
}
```

## Sub-module organisation

Group related scenarios under nested modules — see `pinned_test.rs`, `catalog_defs_test.rs`:

```rust
mod local {
  use super::*;
  #[tokio::test] async fn refuses_to_pin_local_version() { ... }
}

mod normal {
  use super::*;
  #[tokio::test] async fn an_already_pinned_version_is_valid() { ... }
}

mod registry_updates {
  use super::*;
  #[tokio::test] async fn def_marked_outdated_when_registry_has_newer_version() { ... }
}
```

## File location

| Test type                                    | Location                                    |
| -------------------------------------------- | ------------------------------------------- |
| Version-group behaviour (pin, ban, ranges …) | `src/version_group/<group>_test.rs`         |
| Catalog discovery wiring                     | `src/version_group/{catalog,bun_catalog}_test.rs` |
| Fix mutations                                | `src/commands/fix_test.rs`                  |
| Format pass                                  | `src/visit_formatting/format_test.rs`       |
| Other unit tests                             | Co-located: `src/foo.rs` ↔ `src/foo_test.rs` |

## Builder methods

Source of truth: `src/test/builder.rs`.

| Method                                                  | Purpose                                                              |
| ------------------------------------------------------- | -------------------------------------------------------------------- |
| `.with_package(json!({...}))`                           | Add one package.json                                                 |
| `.with_packages(vec![...])`                             | Add many                                                             |
| `.with_version_group(json!({...}))`                     | Add one version group                                                |
| `.with_version_groups(vec![...])`                       | Add many                                                             |
| `.with_semver_group(json!({...}))`                      | Add a semver group                                                   |
| `.with_config(json!({...}))`                            | Base config (e.g. `customTypes`, `dependencyGroups`)                 |
| `.with_strict(bool)`                                    | Strict mode (Suspect → error)                                        |
| `.with_subcommand("update")`                            | Override subcommand (default: `lint`, or `update` if registry set)   |
| `.with_pnpm_catalogs(yaml)`                             | Inject `pnpm-workspace.yaml`; implies pnpm PM                        |
| `.with_bun_catalogs(json!({...}))`                      | Synthetic Bun root with `/catalog`, `/catalogs/{n}`; implies Bun PM  |
| `.with_bun_workspaces_catalogs(json!({...}))`           | Same, nested under `/workspaces/`                                    |
| `.with_{pnpm,bun,npm,yarn,unknown}_package_manager()`   | Force PM detection                                                   |
| `.with_registry_updates(json!({"react":[...]}))`        | Mock npm registry; implies subcommand=`update`                       |
| `.with_update_target(UpdateTarget::Minor)`              | Bound update target                                                  |
| `.run().await` → `Context`                              | **Primary** end-to-end (full pipeline through disk + discovery)      |
| `.build()`                                              | Sync, no visit — context-wiring tests                                |
| `.build_and_visit_packages()`                           | Sync + visit_packages — fix tests, older suites                      |
| `.build_and_visit_formatting()`                         | Sync + visit_formatting                                              |
| `.build_with_registry_and_visit().await`                | Sync wiring + async registry mock + visit                            |

## ExpectedInstance fields

```rust
ExpectedInstance {
  state: InstanceState::fixable(DiffersToPin),  // valid / fixable / unfixable / suspect
  dependency_name: "react",                     // = `internal_name` (alias-aware)
  id: "react in /dependencies of package-a",    // {dep} in {/path} of {package_or_yaml}
  actual: "17.0.0",                             // raw specifier on disk
  expected: Some("18.0.0"),                     // None = ignore; Some("") = remove
  overridden: None,                             // semver-group override target, if any
}
```

`id` location examples:

- `/dependencies`, `/devDependencies`, `/peerDependencies`
- `/version of package-a` (local version)
- `/packageManager`, `/engines/node`
- `/catalog of pnpm-workspace.yaml`, `/catalogs/<name> of pnpm-workspace.yaml`
- `/customVersion`, `/custom/config/version` (via `customTypes`)

## Patterns

→ [patterns.md](patterns.md): banned, pinned, sameRange, pnpm catalogs, bun catalogs, semver ranges, registry updates.

## Fix tests

`src/commands/fix_test.rs` builds with `.build_and_visit_packages()` then runs `fix::run(ctx, &SilentReporter, &disk)`. `dry_run = true` is the default (set by `mock::config_from_mock`), so `is_dirty()` and post-fix contents stay observable. Set `ctx.config.cli.dry_run = false` only when asserting writes through a recording `MockDiskIo` (see `pnpm_fix_writes_yaml_to_disk`).

## Common mistakes

| Wrong                                            | Right                                                                                  |
| ------------------------------------------------ | -------------------------------------------------------------------------------------- |
| `#[test] fn foo()` + `.run().await`              | `#[tokio::test] async fn foo()`                                                        |
| `use crate::instance_state::*`                   | `use crate::instance::*`                                                               |
| `"pinned": "1.0.0"`                              | `"pinVersion": "1.0.0"`                                                                |
| `Context { ... }`                                | `TestBuilder::new()...`                                                                |
| `ctx.instances[0]`                               | `expect(&ctx).to_have_instances(vec![...])`                                            |
| `.build()` then check states                     | `.run().await` (or `.build_and_visit_packages()` for sync)                             |
| Missing `SuspectInstance::*` import              | Import all 4: `FixableInstance::*, ValidInstance::*, SuspectInstance::*, UnfixableInstance::*` |

## Running

```bash
just test                            # all
cargo test pinned_test               # pattern match
cargo test test_name -- --nocapture  # with stdout
```

## Reference tests

- `src/version_group/banned_test.rs` — banned + custom types
- `src/version_group/pinned_test.rs` — sub-modules, semver-group interaction
- `src/version_group/same_range_test.rs` — range satisfaction
- `src/version_group/catalog_test.rs` — pnpm catalogs
- `src/version_group/bun_catalog_test.rs` — bun catalogs (sync `.build()`)
- `src/version_group/preferred_semver_test.rs` — registry updates, update targets
Files in this package (2)
  • SKILL.md 9 KB
  • patterns.md 12 KB

Versions

RepositoryScoreLicence
This page · JamieMason/syncpack100MIT
onsi/biloba100MIT
runwield/runwield100MIT
dzhng/jevgrep100MIT
lukedj78/dev-flow100MIT
dzhng/skills100MIT
rowansci/rowan-python100MIT
yaohuangguan/agent-playbook100MIT
microsoft/semantic-link-labs100MIT

Database snapshot: 13.11.2026 · package version 9e451d6a8d46 (SHA-256 digest of the entire package)

Downloads in the last 30 days: 0 · ranking

Report a problem with this skill: harmful content, incorrect descriptions or an author’s removal request.

Similar skills

  • 100v1-write-tests
    Use when writing unit tests for code changes or new functionality. Triggers on "write tests", "add tests", "test this code".
  • 90add-feature
    Add new features to Syncpack including commands and validation logic. Use when implementing new CLI commands, adding InstanceState variants, or extending version group behaviour.
  • 100purview-testing-conventions
    Use when writing or reviewing tests in a .NET repository following the Purview conventions - TUnit and TUnit.Mocks, the AAA structure and naming, placing a test in the namespace of the class under test, CreateXXX construction helpers, building domain content through TestBuilder…
  • 100create-unit-test
    Write or refactor unit tests following the canonical project pattern. Use when adding new unit tests or standardizing existing ones.
  • 95tarn-api-testing
    Write, run, debug, and iterate on API tests using Tarn — a CLI-first API testing tool with .tarn.yaml files and structured JSON output. Use when: writing API tests, running tarn commands, debugging test failures, generating tests from OpenAPI specs or curl commands, setting up…
  • 100generate
    Generate Playwright tests. Use when user says "write tests", "generate tests", "add tests for", "test this component", "e2e test", "create test for", "test this page", or "test this feature".

Feedback and reports

Your message goes to Bartosz Sokoliński. An email address is needed only for a reply. See the privacy policy.