Skip to content

Тестирование Module Federation

До тестов подготовьте MF bootstrap, разберите participant scope и реализуйте host/remote contract.

Уровни проверки

УровеньЧто проверяет
TypeScript contractСовместимость bootstrap и API adapter.
UnitBusiness store без React и remote loader.
IntegrationRuntime, scopes, cache и namespace policy.
Browser E2EРеальный mount, HTTP, unmount и два remote.

Compatibility до mount

ts
expect(() => {
    assertQueryRuntimeCompatibility(runtime.getProtocolDescriptor(), {
        protocolVersion: QUERY_RUNTIME_PROTOCOL_VERSION,
        minimumRuntimeVersion: "3.2.0",
        requiredCapabilities: ["query", "mutation"],
    })
}).not.toThrow()

Добавьте отрицательные тесты для protocol mismatch, слишком старого runtime и отсутствующей capability. Remote UI не должен монтироваться после отказа.

Namespace denial

ts
const scope = runtime.createParticipantScope({
    participantId: "support-remote",
    participantType: "remote",
    capabilities: {
        queryNamespaces: [["tickets"]],
        mutationNamespaces: ["tickets"],
    },
})

expect(() => scope.query.getData(["invoices", "list"])).toThrow()
expect(backendMetrics.invoiceRequests).toBe(0)

Главное утверждение: отказ происходит до network.

Private isolation двух mount

ts
const first = runtime.createParticipantScope(registration)
const second = runtime.createParticipantScope(registration)

expect(first.identity.participantId).toBe(second.identity.participantId)
expect(first.identity.instanceId).not.toBe(second.identity.instanceId)

Запишите разные данные в одинаковый logical key и убедитесь, что каждый mount видит только свой snapshot.

Shared query и deduplication

Оба participant должны получить разрешённый shared namespace и стабильный definitionId:

ts
const createDictionaryStore = (scope: IScopedQueryRuntime) =>
    scope.query.createStore<Dictionary>({
        definitionId: "shared.dictionary.v1",
        queryKey: () => ["shared", "dictionary"],
        queryFn: ({ signal }) => dictionaryApi.load({ signal }),
    })

Запустите два stores одновременно и проверьте один backend request. Затем создайте store с тем же key и другим definitionId: cache должен выбросить QueryDefinitionConflictError до второго backend request.

Unmount и диагностика

ts
const before = runtime.getDiagnostics()

unmountRemote()
scope.dispose()

const after = runtime.getDiagnostics()

expect(after.activeParticipantCount).toBe(before.activeParticipantCount - 1)

Также проверьте отмену owned direct request и отсутствие отмены shared request, который всё ещё нужен другому participant.

Playwright стенд проекта

В playwright/ уже есть browser-сценарии:

  • два QueryStore дедуплицируют HTTP;
  • Mutation обновляет активный Query;
  • InfiniteQueryStore загружает следующую страницу;
  • FetchStore выполняет auth refresh;
  • RequestStore отменяет slow request;
  • scope запрещает чужой namespace;
  • React frontend вызывает API через business stores.

Запуск:

bash
npm run playwright:install
npm run playwright:test

API-проверки без запуска Chromium:

bash
npm run playwright:test:api