PageSourceSearch

https://inversify.io/assets/js/dbeea9e6.2d803d2e.js

js inversify.io collected 2026-10-02 08:16:49 UTC 13,302 bytes, 1 lines download raw bytes

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.