Every @gorgo plugin runs unit, integration, and contract tests against real provider APIs every day and is re-verified on the latest Medusa release, so your stores keep running reliably. Here is how the pipeline works.

When you install a payment or fulfillment plugin, you hand it your checkout, and a bug there costs real money. That is why testing for every package in the medusa-plugins monorepo is fully automated, from unit tests to daily checks against live provider APIs. This post covers the tests we run, how much of each plugin they cover, and how compatibility with the latest Medusa release is verified.
The repository currently holds 81 test files and 543 test cases across six plugins.
Unit tests exercise a provider's logic in isolation, with no network and no database: status mapping, signature generation, receipt formatting, error parsing. Outbound HTTP is intercepted with MSW, which makes it easy to check how the plugin reacts to any provider response, including scenarios that are hard to reproduce by hand: a declined payment, a malformed webhook, a replayed event.
Integration tests live in separate packages and boot a full Medusa application with the plugin installed and a PostgreSQL database, using . A test database is created and dropped on every run. HTTP tests walk admin and store routes end to end; module tests call services directly. We borrowed part of the approach — the test layout and the database handling — from Medusa's own repository, where these tools are used to test the core.
Unit and integration tests mock the external service, so neither will notice a change on its side. Contract tests do the opposite: they call the provider's API (YooKassa, T-Kassa, Robokassa, ApiShip) and verify that the request and response shapes the plugin relies on still match reality.
In CI these tests run daily with active API keys. Any breaking change on the provider's side, be it a renamed field, a new status, or stricter validation, leads to test failures. Here, for example, is the check that will notice a new payment lifecycle status:
1const KNOWN_STATUSES = [2 "pending",3 "waiting_for_capture",4 "succeeded",5 "canceled",6] as const7
8// ...9
10let client: YooCheckout11
12beforeAll(() => {13 client = new YooCheckout({ shopId: SHOP_ID!, secretKey: SECRET_KEY! })14})15
16it("createPayment → getPayment → cancelPayment honours the documented lifecycle contract", async () => {17 const idempotencyKey = `contract-no-receipt-${Date.now()}`18 const payment = await sdkCall(() =>19 client.createPayment(20 {21 amount: { value: "10.00", currency: "RUB" },22 confirmation: { type: "redirect", return_url: "https://example.com/return" },23 description: "medusa-plugin contract test (no receipt)",24 metadata: { session_id: idempotencyKey, receip_tmp: "{}" },25 },26 idempotencyKey27 )28 )29
30 expect(payment.id).toBeDefined()31 expect(payment.status).toBeDefined()32 expect(KNOWN_STATUSES).toContain(payment.status as any)33 expect(payment.amount.value).toBe("10.00")34 expect(payment.amount.currency).toBe("RUB")35 expect(payment.confirmation).toBeDefined()36 expect((payment.confirmation as any).confirmation_url).toMatch(/^https:\/\//)37 // ...38})Coverage varies. Payment plugins are tested most deeply, with unit, integration, and contract tests, because a payment bug is the most expensive kind. has the largest codebase and the largest test suite. The plugin is in development: for now it has only a basic smoke test.
Here are the current numbers for each plugin. The percentages are estimates of testing depth, not exact line coverage.
| Plugin | Unit | Integration | Contract | Coverage | Badge |
|---|---|---|---|---|---|
| 88 | 11 | 4 | ~90% | ||
| 76 | 11 | 5 | ~90% | ||
| 60 | 7 | 4 | ~85% | ||
| 149 | 80 | 19 | ~80% | ||
| 0 | 21 | 0 | ~60% | ||
| 0 | 1 | 0 | ~5% |
The badge in each plugin's README updates automatically. Every morning the workflow runs the following steps:
Plugins are isolated from each other: if one breaks on a new Medusa release, it is excluded from the update while the rest keep being tested.
Here is what that looks like in practice. On June 25 the morning run picked up the fresh Medusa v2.17.1 and updated and tested all six plugins in about ten minutes. Five passed, and failed to build: the new version dropped the field from the type. The plugin stayed on its previous verified version, and the breakage is visible in pull request #358.
The badge itself is a JSON file in the repository, rendered by shields.io. A in it means the plugin's tests actually passed on that version:
1{2 "schemaVersion": 1,3 "label": "Tested with Medusa",4 "message": "v2.17.1",5 "color": "green"6}Three GitHub Actions workflows drive the automation:
On pull requests, runs tests only for the plugins that changed. The nightly run and manual runs test everything.
1medusa-plugins/2├── examples/ # example Medusa projects with the plugins integrated, always on the latest Medusa3├── integration-tests/ # Medusa installs with integration and contract tests, one per plugin4├── packages/ # plugin npm packages, unit tests next to the code5├── .badges/ # "Tested with Medusa" JSON per plugin6├── scripts/ # update to a new Medusa and rebuild badges7├── .github/workflows/ # test, update-medusa-version, publish8└── ...All the tests are public, browse them in the gorgojs/medusa-plugins repository and read about the plugins in the documentation. Ask questions and report bugs in the Gorgo support chat or the Medusa Telegram community. Issues and Pull Requests are always welcome.