1"use strict";(self.webpackChunk_inversifyjs_inversify_docs_site=self.webpackChunk_inversifyjs_inversify_docs_site||[]).push([["4929"],{90216(e,n,t){t.r(n),t.d(n,{metadata:()=>i,default:()=>p,frontMatter:()=>c,contentTitle:()=>o,toc:()=>d,assets:()=>l});var i=JSON.parse('{"id":"ecosystem/suites","title":"Suites","description":"Unit testing in dependency injection systems often involves repetitive setup and brittle mock wiring. Suites is a unit","source":"@site/docs/ecosystem/suites.mdx","sourceDirName":"ecosystem","slug":"/ecosystem/suites","permalink":"/docs/next/ecosystem/suites","draft":false,"unlisted":false,"tags":[],"version":"current","sidebarPosition":4,"frontMatter":{"sidebar_position":4,"title":"Suites"},"sidebar":"tutorialSidebar","previous":{"title":"Strongly Typed Container","permalink":"/docs/next/ecosystem/strongly-typed"},"next":{"title":"Planning phase","permalink":"/docs/next/internals/planning"}}'),s=t(89292),a=t(19736),r=t(85817);let c={sidebar_position:4,title:"Suites"},o="Suites",l={},d=[{value:"When to use it",id:"when-to-use-it",level:2},{value:"Core features",id:"core-features",level:2},{value:"Installation",id:"installation",level:2},{value:"Quick Examples",id:"quick-examples",level:2},{value:"Solitary Testing",id:"solitary-testing",level:3},{value:"Sociable Testing with Collaboration",id:"sociable-testing-with-collaboration",level:3},{value:"Token Dependencies",id:"token-dependencies",level:3},{value:"InversifyJS Metadata Support",id:"inversifyjs-metadata-support",level:3},{value:"How it works",id:"how-it-works",level:2},{value:"Documentation",id:"documentation",level:2}];function u(e){let n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,a.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"suites",children:"Suites"})}),"\n",(0,s.jsx)(n.p,{children:"Unit testing in dependency injection systems often involves repetitive setup and brittle mock wiring. Suites is a unit\ntesting framework that creates isolated test environments with a single declarative call, helping developers focu
1s on\ntest logic rather than infrastructure, boilerplate and setup."}),"\n",(0,s.jsx)(n.h2,{id:"when-to-use-it",children:"When to use it"}),"\n",(0,s.jsx)(n.p,{children:"Use Suites when you:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Need fast, maintainable unit tests for InversifyJS classes"}),"\n",(0,s.jsx)(n.li,{children:"Want compile-time type safety for all mocked dependencies"}),"\n",(0,s.jsx)(n.li,{children:"Need to eliminate repetitive mock setup boilerplate"}),"\n",(0,s.jsx)(n.li,{children:"Want to test classes with both isolation and selective collaboration"}),"\n",(0,s.jsx)(n.li,{children:"Need AI-assisted testing with clear, structured patterns"}),"\n"]}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsx)(n.p,{children:"If you need full integration tests with real dependencies and actual InversifyJS containers, traditional\ncontainer configuration may be more appropriate."})}),"\n",(0,s.jsx)(n.h2,{id:"core-features",children:"Core features"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Auto-generates type-safe mocks"})," - All dependencies become fully typed ",(0,s.jsx)(n.code,{children:"Mocked<T>"})," instances with compile-time validation"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Solitary and sociable testing"})," - Test classes in complete isolation or enable selective collaboration between business logic"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"InversifyJS-aware"})," - Recognizes ",(0,s.jsx)(n.code,{children:"@injectable"}),", ",(0,s.jsx)(n.code,{children:"@inject"}),", ",(0,s.jsx)(n.code,{children:"@multiInject"}),", ",(0,s.jsx)(n.code,{children:"@tagged"}),", and ",(0,s.jsx)(n.code,{children:"@named"})," decorators automatically"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Virtual DI container"})," - No actual InversifyJS container needed - analyzes classes and creates only required mocks"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Token auto-mocking"})," - Token-injected dependencies are always mocked, ensuring tests remain unit tests"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Framework integration"})," - Works with Jest, Vitest, and Sinon out of the box"]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"installation",children:"Installation"}),"\n",(0,s.jsx)(n.p,{children:"For InversifyJS 6.x:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm install -D @suites/unit @suites/di.inversify@^3.0.0 @suites/doubles.jest\n"})}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsxs)(n.p,{children:["Use ",(0,s.jsx)(n.code,{children:"@suites/doubles.vitest"})," or ",(0,s.jsx)(n.code,{children:"@suites/doubles.sinon"})," if you're using Vitest or Sinon instead of Jest."]})}),"\n",(0,s.jsxs)(n.p,{children:["Add type references to ",(0,s.jsx)(n.code,{children:"global.d.ts"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'/// <reference types="@suites/doubles.jest/unit" />\n/// <reference types="@suites/di.inversify" />\n'})}),"\n",(0,s.jsx)(n.h2,{id:"quick-examples",children:"Quick Examples"}),"\n",(0,s.jsx)(n.h3,{id:"solitary-testing",children:"Solitary Testing"}),"\n",(0,s.jsx)(n.p,{children:"Test a class in complete isolation with all dependencies automatically mocked:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",metastring:'title="order.service.ts"',children:"@injectable()\nclass OrderService {\n constructor(\n private readonly paymentGateway: PaymentGateway,\n private readonly inventoryService: InventoryService\n ) {}\n\n async processOrder(orderId: string) {\n const inventory = await this.inventoryService.checkStock(orderId);\n return this.paymentGateway.charge(inventory.total);\n }\n}\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",metastring:'title="order.service.spec.ts"',children:"import { TestBed, type Mocked } from '@suites/unit';\nimport { injectable } from 'inversify';\n\ndescribe('Order Service Unit Spec', () => {\n let orderService: OrderService;\n let paymentGateway: Mocked<PaymentGateway>;\n let inventoryService: Mocked<InventoryService>;\n\n beforeAll(async () => {\n const { unit, unitRef } = await TestBed.solitary(OrderService).compile();\n\n orderService = unit;\n paymentGateway = unitRef.get(PaymentGateway);\n inventoryService = unitRef.get(InventoryService);\n });\n\n it('should process order with payment', async () => {\n inventoryService.checkStock.mockResolvedValue({ total: 100 });\n paymentGateway.charge.mockResolvedValue({ success: true });\n\n const result = await orderService.processOrder('123');\n\n expect(result.success).toBe(true);\n expect(paymentGateway.charge).toHaveBeenCalledWith(100);\n });\n});\n"})}),"\n",(0,s.jsx)(n.h3,{id:"sociable-testing-with-collaboration",children:"Sociable Testing with Collaboration"}),"\n",(0,s.jsx)(n.p,{children:"Test classes with their real collaborators while mocking external boundaries:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:"@injectable()\nclass PriceCalculator {\n calculateTotal(items: Item[]): number {\n return items.reduce((sum, item) => sum + item.price, 0);\n }\n}\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:"@injectable()\nclass OrderService {\n
1constructor(\n private calculator: PriceCalculator,\n private paymentGateway: PaymentGateway\n ) {}\n\n async processOrder(items: Item[]) {\n const total = this.calculator.calculateTotal(items);\n return this.paymentGateway.charge(total);\n }\n}\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:"import { TestBed, type Mocked } from '@suites/unit';\n\ndescribe('Order Service Sociable Unit Spec', () => {\n let orderService: OrderService;\n let paymentGateway: Mocked<PaymentGateway>;\n\n beforeAll(async () => {\n const { unit, unitRef } = await TestBed.sociable(OrderService)\n .collaborate() // All business logic collaborates\n .exclude([PaymentGateway]) // Exclude external services\n .compile();\n\n orderService = unit;\n paymentGateway = unitRef.get(PaymentGateway);\n });\n\n it('should calculate and charge correct total', async () => {\n paymentGateway.charge.mockResolvedValue({ success: true });\n\n await orderService.processOrder([\n { price: 10 },\n { price: 20 }\n ]);\n\n // Real calculator logic executed, only gateway is mocked\n expect(paymentGateway.charge).toHaveBeenCalledWith(30);\n });\n});\n"})}),"\n",(0,s.jsx)(n.h3,{id:"token-dependencies",children:"Token Dependencies"}),"\n",(0,s.jsxs)(n.p,{children:["Token-injected dependencies are always auto-mocked (read more about ",(0,s.jsx)(n.a,{href:"/docs/introduction/dependency-inversion/",children:"Dependency Inversion"}),"):"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:"@injectable()\nclass PaymentService {\n constructor(\n private validator: PaymentValidator, // Class dependency\n @inject('DATABASE') private database: Database, // Token dependency\n ) {}\n}\n\ndescribe('PaymentService', () => {\n let service: PaymentService;\n let validator: Mocked<PaymentValidator>;\n let database: Mocked<Database>;\n\n beforeAll(async () => {\n const { unit, unitRef } = await TestBed.solitary(PaymentService).compile();\n\n service = unit;\n validator = unitRef.get(PaymentValidator); // By class\n database = unitRef.get<Database>('DATABASE'); // By token\n });\n});\n"})}),"\n",(0,s.jsx)(n.h3,{id:"inversifyjs-metadata-support",children:"InversifyJS Metadata Support"}),"\n",(0,s.jsx)(n.p,{children:"Suites fully supports InversifyJS metadata patterns for tagged and named bindings:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:"@injectable()\nclass Ninja {\n constructor(\n @inject('Weapon') @tagged('canThrow', false) private katana: Weapon,\n @inject('Weapon') @tagged('canThrow', true) private shuriken: Weapon\n ) {}\n}\n\ndescribe('Ninja', () => {\n let ninja: Ninja;\n let katana: Mocked<Weapon>;\n let shuriken: Mocked<Weapon>;\n\n beforeAll(async () => {\n const { unit, unitRef } = await TestBed.solitary(Ninja).compile();\n\n ninja = unit;\n\n // Access mocks by tag metadata\n katana = unitRef.get<Weapon>('Weapon', { canThrow: false });\n shuriken = unitRef.get<Weapon>('Weapon', { canThrow: true });\n });\n\n it('should use correct weapons', () => {\n // Both mocks are fully typed and independent\n expect(katana).toBeDefined();\n expect(shuriken).toBeDefined();\n });\n});\n"})}),"\n",(0,s.jsx)(n.p,{children:"You can also configure mocks with metadata before compilation:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:"await TestBed.solitary(Ninja)\n .mock<Weapon>('Weapon', { canThrow: true })\n .impl(stub => ({\n attack: stub.mockReturnValue('shuriken attack!')\n }))\n .compile();\n"})}),"\n",(0,s.jsx)(n.h2,{id:"how-it-works",children:"How it works"}),"\n",(0,s.jsxs)(n.p,{children:["Suites uses a ",(0,s.jsx)(n.strong,{children:"virtual DI container"})," that analyzes Inversify's classes without initializing an actual container."]}),"\n",(0,s.jsx)(n.p,{children:"It:"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["Detects ",(0,s.jsx)(n.code,{children:"@injectable"})," and ",(0,s.jsx)(n.code,{children:"@inject"})," decorators automatically"]}),"\n",(0,s.jsx)(n.li,{children:"Creates type-safe mocks for all dependencies"}),"\n",(0,s.jsx)(n.li,{children:"Wires them together without framework overhead"}
1),"\n",(0,s.jsx)(n.li,{children:"Ensures token dependencies are always mocked (keeps tests as unit tests, not integration tests)"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This means you get the testing ergonomics of a DI framework without the initialization cost or complexity."}),"\n",(0,s.jsx)(n.h2,{id:"documentation",children:"Documentation"}),"\n",(0,s.jsx)(n.p,{children:"Full documentation with guides, API references, and examples:"}),"\n",(0,s.jsx)(r.A,{href:"https://suites.dev",target:"_blank",children:(0,s.jsx)("span",{children:"View Full Documentation"})})]})}function p(e={}){let{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(u,{...e})}):u(e)}},85817(e,n,t){t.d(n,{A:()=>a});var i=t(89292),s=t(52769);t(83888);function a({href:e,children:n,target:t}){return(0,i.jsx)("div",{className:"buttonContainer_jyGP",children:(0,i.jsxs)(s.A,{to:e,target:t,rel:"noopener noreferrer",className:"documentationButton_Sex6",children:[(0,i.jsx)("span",{className:"buttonText_NSOW",children:n}),(0,i.jsx)("span",{className:"buttonIcon_fV4z",children:"\u{1F4D6}"}),(0,i.jsx)("div",{className:"shimmer_GGMR"})]})})}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.