diff --git a/docs/api-reference/fail-fast.md b/docs/api-reference/fail-fast.md index 95ae298..c24d190 100644 --- a/docs/api-reference/fail-fast.md +++ b/docs/api-reference/fail-fast.md @@ -120,14 +120,15 @@ const logger = unitRef.get(Logger); logger.log.mockReturnValue(undefined); ``` -### Option 3: Use .boundaries() (Best for Sociable) +### Option 3: Use .collaborate() + .exclude() (Best for Sociable) -Switch to boundaries pattern for cleaner configuration: +Switch to collaborate pattern for cleaner configuration: ```typescript // Instead of configuring many real dependencies const { unit } = await TestBed.sociable(OrderService) - .boundaries([ComplexTaxEngine]) // Avoid complex logic + .collaborate() + .exclude([ComplexTaxEngine]) // Exclude from collaboration .compile(); // Note: Token-injected deps (DATABASE, HTTP) are auto-mocked @@ -158,20 +159,21 @@ await TestBed.sociable(PaymentService) - Unconfigured mocks throw immediately - Bug caught at test time ✅ -### Less Critical in Boundaries Mode +### Less Critical in Collaborate Mode -With `.boundaries()`, the default is **everything real**: +With `.collaborate()`, the default is **everything real**: ```typescript await TestBed.sociable(OrderService) - .boundaries([ComplexMLService]) // Avoid complex logic + .collaborate() + .exclude([ComplexMLService]) // Exclude from collaboration .compile(); // All other deps try to instantiate as real // If deps missing → natural constructor failure (already caught) ``` -Fail-fast still helps, but boundaries mode naturally fails if dependencies are misconfigured. +Fail-fast still helps, but collaborate mode naturally fails if dependencies are misconfigured. ## Differences Between Test Types @@ -194,9 +196,10 @@ const { unit } = await TestBed.sociable(Service) .expose(RealService) .compile(); -// Boundaries mode: Default is real → natural failures +// Collaborate mode: Default is real → natural failures const { unit } = await TestBed.sociable(Service) - .boundaries([ComplexService]) + .collaborate() + .exclude([ComplexService]) .compile(); ``` @@ -205,13 +208,13 @@ const { unit } = await TestBed.sociable(Service) ## Best Practices 1. **Configure used methods**: Only mock methods your test actually calls -2. **Use .boundaries() for test scope**: List classes to avoid (complex logic tested elsewhere, legacy code, third-party SDKs) +2. **Use .collaborate() + .exclude() for test scope**: Exclude classes you want to avoid (complex logic tested elsewhere, legacy code, third-party SDKs) 3. **Tokens are auto-mocked**: Token-injected dependencies (`@Inject('DB')`) are automatically mocked 4. **Migrate gradually**: Use `.failFast({ enabled: false })` temporarily 5. **Remove `.failFast({ enabled: false })`**: Complete migration before v5.0.0 ## See Also -- [TestBed.sociable()](/docs/api-reference/testbed-sociable) - Sociable test configuration (includes `.expose()` and `.boundaries()`) +- [TestBed.sociable()](/docs/api-reference/testbed-sociable) - Sociable test configuration (includes `.expose()` and `.collaborate() + .exclude()`) - [Mock Configuration](/docs/api-reference/mock-configuration) - Configuring mock behavior - [Migration Guide](/docs/migration-guides/from-automock) - Migrating to Suites \ No newline at end of file diff --git a/docs/api-reference/index.md b/docs/api-reference/index.md index 0ccee53..7c6c45a 100644 --- a/docs/api-reference/index.md +++ b/docs/api-reference/index.md @@ -13,7 +13,7 @@ API reference for setting up and managing unit tests with Suites. ## Core APIs - [**TestBed.solitary()**](/docs/api-reference/testbed-solitary) - Create isolated unit tests where all dependencies are automatically mocked -- [**TestBed.sociable()**](/docs/api-reference/testbed-sociable) - Test business logic interactions with `.boundaries()` v4.0.0+ or `.expose()` +- [**TestBed.sociable()**](/docs/api-reference/testbed-sociable) - Test business logic interactions with `.collaborate()` + `.exclude()` v4.0.0+ or `.expose()` - [**Mock Configuration**](/docs/api-reference/mock-configuration) - Configure mock behavior with `.mock().final()` and `.mock().impl()` - [**mock() and stub()**](/docs/api-reference/mock) - Create standalone mocks outside TestBed - [**UnitReference**](/docs/api-reference/unit-reference) - Access mocked dependencies in tests @@ -31,9 +31,10 @@ const { unit, unitRef } = await TestBed.solitary(UserService).compile(); ### Creating a Sociable Test ```typescript -// Recommended: boundaries (v4.0.0+) +// Recommended: collaborate + exclude (v4.0.0+) const { unit, unitRef } = await TestBed.sociable(UserService) - .boundaries([ComplexService]) // List what to avoid + .collaborate() + .exclude([ComplexService]) // Exclude from collaboration .compile(); // Alternative: expose diff --git a/docs/api-reference/testbed-sociable.md b/docs/api-reference/testbed-sociable.md index f59d5dc..6e65b00 100644 --- a/docs/api-reference/testbed-sociable.md +++ b/docs/api-reference/testbed-sociable.md @@ -30,17 +30,17 @@ TestBed.sociable(ClassUnderTest: Type): SociableTestBuilder `SociableTestBuilder` with two configuration modes: -### .boundaries() v4.0.0+ +### .collaborate() + .exclude() v4.0.0+ -Recommended approach. List classes to avoid - everything else runs real. +Recommended approach. Enable natural collaboration, then exclude specific classes. ```typescript -boundaries(): SociableTestBuilder -boundaries(dependencies: Type[]): SociableTestBuilder +collaborate(): SociableTestBuilderInCollaborateMode +exclude(dependencies: [Type, ...Type[]]): SociableTestBuilderInCollaborateMode ``` :::tip Token Auto-Mocking -Token-injected dependencies are automatically mocked. Use .boundaries() for class dependencies you want to avoid. +Token-injected dependencies are automatically mocked. Use `.exclude()` for class dependencies you want to opt-out of collaboration. ::: ### .expose() - Alternative @@ -57,17 +57,18 @@ Both methods only accept class constructors, not tokens. ## Examples -### Using .boundaries() +### Using .collaborate() + .exclude() ```typescript const { unit, unitRef } = await TestBed.sociable(OrderService) - .boundaries([ComplexTaxEngine]) // Avoid complex logic + .collaborate() + .exclude([ComplexTaxEngine]) // Exclude from collaboration .compile(); -// Can retrieve boundaries (mocked) +// Can retrieve excluded dependencies (mocked) const taxEngine = unitRef.get(ComplexTaxEngine); -// Cannot retrieve real dependencies +// Cannot retrieve collaborating dependencies // const calculator = unitRef.get(PriceCalculator); // ERROR - it's real ``` @@ -87,11 +88,11 @@ const database = unitRef.get(Database); ## What's Retrievable -**Boundaries mode:** -- ✅ Classes in .boundaries() array (mocked) +**Collaborate mode:** +- ✅ Classes in .exclude() array (mocked) - ✅ Tokens (auto-mocked) - ✅ Explicitly mocked dependencies -- ❌ Real dependencies (auto-exposed, leaf classes) +- ❌ Collaborating dependencies (real, auto-exposed) **Expose mode:** - ✅ Non-exposed dependencies (mocked) @@ -101,15 +102,14 @@ const database = unitRef.get(Database); ## Mode Comparison -| Aspect | .boundaries() | .expose() | -|--------|---------------|-----------| -| Default | Everything real | Everything mocked | -| You list | What to avoid | What to keep | +| Aspect | .collaborate() + .exclude() | .expose() | +|--------|----------------------------|-----------| +| Default | Everything collaborates | Everything mocked | +| You list | What to exclude | What to keep | | Use when | Many deps should be real | Few deps should be real | -| Future-proof | ✅ New deps auto-tested | ⚠️ New deps ignored | +| Refactoring-stable | ✅ New deps auto-collaborate | ⚠️ New deps ignored | -**Example:** eight `.expose()` calls vs one `.boundaries()` call achieves the same outcome. See [Sociable Guide] -(/docs/guides/sociable) for the comparison. +**Example:** eight `.expose()` calls vs one `.collaborate()` call achieves the same outcome. See [Sociable Guide](/docs/guides/sociable) for the comparison. ## See Also diff --git a/docs/changelog.md b/docs/changelog.md index edb9656..f87ba23 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -9,7 +9,7 @@ description: Release notes and version history for Suites ## v4.0.0-beta.0 (Current - Testing) :::warning Beta Release -v4.0.0 is in beta for testing. Applies alpha infrastructure improvements **plus** boundaries mode and fail-fast behavior. All functionality will be backported to v3.1.0 soon. +v4.0.0 is in beta for testing. Applies alpha infrastructure improvements **plus** collaborate/exclude API and fail-fast behavior. All functionality will be backported to v3.1.0 soon. ::: **From Alpha:** @@ -21,22 +21,30 @@ v4.0.0 is in beta for testing. Applies alpha infrastructure improvements **plus* **New in Beta:** -**`.boundaries()` API** -Blacklist strategy for sociable tests. List classes to avoid - everything else runs real. +**`.collaborate()` + `.exclude()` API** +Natural collaboration strategy for sociable tests. Enable collaboration, then exclude specific classes. ```typescript TestBed.sociable(OrderService) - .boundaries([ComplexTaxEngine]) // Avoid complex logic + .collaborate() + .exclude([ComplexTaxEngine]) // Exclude from collaboration .compile(); ``` -**Why boundaries:** -- Simpler when most deps should be real -- Future-proof: new dependencies auto-tested -- Leaf classes (no dependencies) automatically real +**Why collaborate + exclude:** +- **Refactoring stable**: Adding dependencies doesn't break tests +- **Natural mental model**: Think collaboration, not avoidance +- **Future-proof**: New dependencies auto-collaborate +- **Clear intent**: "Exclude" is more intuitive than "boundaries" - Tokens always auto-mocked (databases, HTTP) -See [TestBed.sociable()](/docs/api-reference/testbed-sociable) for complete API details. +**The refactoring stability advantage:** + +When you add new dependencies to your production code (e.g., adding a `Logger` or `ValidationService`), tests using `.collaborate()` continue to work because new dependencies automatically join the collaboration. You only need to `.exclude()` specific expensive or external services. + +This is more stable than "leaf-based" strategies where adding intermediate dependencies can break tests because the graph structure changed. + +See [TestBed.sociable()](/docs/api-reference/testbed-sociable) for complete API details and [Sociable Tests Guide](/docs/guides/sociable) for examples. **Fail-Fast Behavior** Enabled by default. Throws `DependencyNotConfiguredError` on unconfigured dependencies. Prevents false positives. @@ -54,7 +62,7 @@ See [Fail-Fast Behavior](/docs/api-reference/fail-fast) for details and migratio # Core package (beta) npm install @suites/unit@beta -# DI adapters (alpha - no boundaries/fail-fast changes) +# DI adapters (alpha - no collaborate/exclude or fail-fast changes) npm install @suites/di.nestjs@alpha # or npm install @suites/di.inversify@alpha @@ -68,7 +76,7 @@ npm install @suites/doubles.sinon@beta ``` :::tip Why Different Versions? -`@suites/unit` and doubles adapters are on **beta** (boundaries + fail-fast features). DI adapters remain on **alpha** - they're installed separately and don't contain the new testing logic. +`@suites/unit` and doubles adapters are on **beta** (collaborate/exclude + fail-fast features). DI adapters remain on **alpha** - they're installed separately and don't contain the new testing logic. ::: **Breaking Changes:** @@ -89,7 +97,7 @@ All v4.0.0 features will be backported to v3.1.0 with no breaking changes. This ::: **What's coming:** -- `.boundaries()` API (opt-in) +- `.collaborate()` + `.exclude()` API (opt-in) - Fail-fast behavior (disabled by default, opt-in with .failFast(\{ enabled: true \})) - Enhanced type safety - Performance improvements diff --git a/docs/get-started/installation.mdx b/docs/get-started/installation.mdx index 503e174..c693566 100644 --- a/docs/get-started/installation.mdx +++ b/docs/get-started/installation.mdx @@ -18,7 +18,7 @@ Suites v4.0.0+ requires **Node.js 20 or higher**. Check your version with `node ## Installation :::tip Testing Beta? -For v4.0.0 beta with boundaries and fail-fast features, see [Changelog](/docs/changelog) for beta installation instructions. +For v4.0.0 beta with collaborate/exclude and fail-fast features, see [Changelog](/docs/changelog) for beta installation instructions. ::: Install Suites' core package: diff --git a/docs/guides/fundamentals.md b/docs/guides/fundamentals.md index c34c8e9..9c2aca0 100644 --- a/docs/guides/fundamentals.md +++ b/docs/guides/fundamentals.md @@ -110,7 +110,7 @@ Test one class in complete isolation. All dependencies are mocked. See [Solitary Unit Tests](/docs/guides/solitary) for examples and usage. **Sociable Tests** -Test multiple business logic classes together. Use `.boundaries()` to list classes you want to avoid. +Test multiple business logic classes together. Use `.collaborate()` + `.exclude()` to enable natural collaboration and exclude specific classes. See [Sociable Unit Tests](/docs/guides/sociable) for examples and usage. diff --git a/docs/guides/sociable.md b/docs/guides/sociable.md index 65e2720..1224132 100644 --- a/docs/guides/sociable.md +++ b/docs/guides/sociable.md @@ -47,14 +47,14 @@ class UserService { Two ways to configure sociable tests: -**Option A: .boundaries() v4.0.0+** - List what to avoid, everything else is real +**Option A: .collaborate() + .exclude() v4.0.0+** - Natural collaboration, opt-out specific classes \ **Option B: .expose()** - List what's real, everything else is mocked -#### Step 2a: Using .boundaries() (Recommended) +#### Step 2a: Using .collaborate() + .exclude() (Recommended) -To test `UserService` with a real `UserApi`, list only what you DON'T want to test: +To test `UserService` with real dependencies collaborating naturally: -```typescript title="user.service.spec.ts (boundaries mode)" {1,14} showLineNumbers +```typescript title="user.service.spec.ts (collaborate mode)" {1,14} showLineNumbers import { TestBed, Mocked } from '@suites/unit'; import { UserService } from './user.service'; import { HttpService, Database } from './services'; @@ -66,14 +66,14 @@ describe('UserService Integration Tests', () => { let httpService: Mocked; beforeAll(async () => { - // No boundaries needed - UserApi becomes real automatically! + // All dependencies collaborate naturally - UserApi becomes real! const { unit, unitRef } = await TestBed.sociable(UserService) - .boundaries() // No boundaries - all business logic is real + .collaborate() // Enable natural collaboration .compile(); userService = unit; - // Retrieve the mocked dependencies + // Retrieve the mocked dependencies (tokens are auto-mocked) database = unitRef.get(Database); httpService = unitRef.get(HttpService); }); @@ -93,32 +93,33 @@ describe('UserService Integration Tests', () => { ``` **What happens here:** -- **UserApi**: Real (automatically, since not in boundaries array) -- **Database**: Mocked (automatically) -- **HttpService**: Mocked (automatically) +- **UserApi**: Real (collaborates naturally with UserService) +- **Database**: Mocked (token injection, auto-mocked) +- **HttpService**: Mocked (token injection, auto-mocked) -**Benefits:** Simpler configuration, future-proof. +**Benefits:** Natural collaboration, refactoring-stable, future-proof. -#### Boundaries with Specific Classes +#### Excluding Specific Classes from Collaboration -When avoiding specific business logic classes: +When you need to exclude expensive or external dependencies: ```typescript const { unit, unitRef } = await TestBed.sociable(OrderService) - .boundaries([ComplexTaxEngine]) // Avoid complex tax logic + .collaborate() + .exclude([ComplexTaxEngine]) // Exclude complex tax logic from collaboration .compile(); -// ComplexTaxEngine is in boundaries - it's mocked +// ComplexTaxEngine is excluded - it's mocked const taxEngine = unitRef.get(ComplexTaxEngine); taxEngine.calculate.mockReturnValue(100); await unit.processOrder(order); -// Verify interactions with the boundary +// Verify interactions with excluded dependency expect(taxEngine.calculate).toHaveBeenCalledWith(order.total); ``` -**Key point:** Classes in `.boundaries()` are mocked. You can configure them and verify their interactions - they act as controlled boundary points in your test. +**Key point:** Classes in `.exclude()` are mocked and retrievable. Everything else collaborates naturally with real implementations. #### Step 2b: Using .expose() (Alternative) @@ -179,7 +180,7 @@ Mock configuration works the same in both modes. You can define behavior for moc ```typescript title="Configuring mocks before compilation" beforeAll(async () => { const { unit, unitRef } = await TestBed.sociable(UserService) - .boundaries() // All business logic real + .collaborate() // All business logic collaborates .mock(Database) // Configure specific mock behavior .final({ saveUser: async () => 42 // Fixed return value @@ -214,20 +215,42 @@ Even though sociable tests let you test real interactions, they should still foc **2. Choose the Right Mode** -**.boundaries() works best when:** +**.collaborate() + .exclude() works best when:** - Services have many business logic dependencies - You want new dependencies tested automatically -- You need future-proof tests +- You need refactoring-stable, future-proof tests +- You only need to exclude a few expensive/external classes **.expose() works best when:** - You want to test 2-3 specific class interactions - You need fine-grained control over what's real -- You prefer explicit configuration +- You prefer explicit whitelisting configuration **3. Complementary to Solitary Tests** Sociable tests complement solitary tests - they don't replace them. Use solitary tests for detailed behavior verification and sociable tests for validating interactions. +**4. Refactoring Stability** + +The `.collaborate() + .exclude()` API provides superior refactoring stability compared to other approaches: + +```typescript +// With .collaborate() + .exclude() +TestBed.sociable(OrderService) + .collaborate() + .exclude([ExpensiveMLService]) // Only exclude what's expensive + .compile(); + +// ✅ Adding new dependencies? Test still works! +// New deps automatically join collaboration +``` + +**Why this matters:** + +When you add new dependencies to your production code (e.g., adding a `Logger`), tests using `.collaborate()` continue to work because new dependencies automatically participate in natural collaboration. You only exclude specific expensive or external services. + +This is more stable than "leaf-based" or "whitelist" strategies where adding dependencies can break tests because they weren't in your configuration list. + ## What's Next? Combining solitary and sociable tests covers both independent behavior and component interactions. diff --git a/docs/guides/virtual-di-container.md b/docs/guides/virtual-di-container.md index 3d938a2..805328b 100644 --- a/docs/guides/virtual-di-container.md +++ b/docs/guides/virtual-di-container.md @@ -96,14 +96,14 @@ By auto-mocking these, Suites ensures **sociable tests are still unit tests** - ```typescript // Even in sociable mode with many real classes await TestBed.sociable(OrderService) - .boundaries() // Everything real! + .collaborate() // Everything collaborates! .compile(); // But @Inject('DATABASE') is still auto-mocked // Tests run fast, never hit real database ``` -This creates **natural test boundaries** without manual configuration. +This creates **natural separation** between business logic (real) and external I/O (mocked) without manual configuration. ## Benefits @@ -122,5 +122,5 @@ See [Adapters](/docs/guides/adapters) for framework-specific details. ## Next Steps - [Solitary Unit Tests](/docs/guides/solitary) - Using the virtual container in isolation mode -- [Sociable Unit Tests](/docs/guides/sociable) - Using with boundaries/expose modes +- [Sociable Unit Tests](/docs/guides/sociable) - Using with collaborate/exclude and expose modes - [API Reference](/docs/api-reference/) - Technical details diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 53b8034..d16a859 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -346,7 +346,6 @@ export default function Home(): JSX.Element { name="description" content="Suites automates mocking and simplifies test setup for dependency injection frameworks like NestJS and InversifyJS, reducing boilerplate code." /> -