본문 바로가기
WIKI 기술 지식 베이스

Utility API로 테스트하기(Testing with Utility APIs)

원문 보기 위키 갱신

프런트엔드 컴포넌트와 확장을 테스트할 때는 종종 그것들이 의존하는 utility API의 목(mock) 구현을 제공해야 해요. @backstage/frontend-test-utils 패키지는 모든 핵심 utility API에 대한 즉시 사용 가능한 목을 담은 mockApis 네임스페이스를 제공해요.

출처: 문서

본문

프런트엔드 컴포넌트와 확장을 테스트할 때는 종종 그것들이 의존하는 utility API의 목(mock) 구현을 제공해야 해요. @backstage/frontend-test-utils 패키지는 모든 핵심 utility API에 대한 즉시 사용 가능한 목을 담은 mockApis 네임스페이스를 제공해요.

mockApis 네임스페이스

mockApis 네임스페이스는 테스트에서 목 utility API 인스턴스를 만드는 주요 진입점이에요. 각 API에 대해 두 가지 사용 패턴을 제공해요.

Fake 인스턴스

API 함수를 직접 호출해 단순하지만 기능하는 동작을 가진 가짜 인스턴스를 만들어요. 이것들은 테스트가 API를 단순히 스텁(stub)으로 두는 게 아니라 실제로 동작하기를 필요로 할 때 유용해요.

import { mockApis } from '@backstage/frontend-test-utils';

const configApi = mockApis.config({ data: { app: { title: 'Test' } } });
configApi.getString('app.title'); // 'Test'

const alertApi = mockApis.alert();
alertApi.post({ message: 'hello' });
expect(alertApi.getAlerts()).toHaveLength(1);

Jest 목(Jest mocks)

.mock()을 호출하면 모든 메서드가 jest.fn()인 인스턴스를 얻어요. 선택적으로 부분 구현을 제공할 수 있어요. 특정 메서드가 호출되었는지를 검증하고 싶을 때 유용해요.

import { mockApis } from '@backstage/frontend-test-utils';
import { AuthorizeResult } from '@backstage/plugin-permission-common';

const permissionApi = mockApis.permission.mock({
  authorize: async () => ({ result: AuthorizeResult.ALLOW }),
});

// ... exercise the component ...
expect(permissionApi.authorize).toHaveBeenCalledTimes(1);

테스트에 목 API 제공하기

renderInTestApp으로

import { screen } from '@testing-library/react';
import { renderInTestApp, mockApis } from '@backstage/frontend-test-utils';

await renderInTestApp(<MyComponent />, {
  apis: [
    mockApis.identity({ userEntityRef: 'user:default/guest' }),
    mockApis.config({ data: { app: { title: 'Test App' } } }),
  ],
});

[apiRef, implementation] 튜플 구문을 사용해 어떤 API 구현이든 제공할 수도 있어요. mockApis에서 나온 것이 아닌 것도 포함해서요.

import { myCustomApiRef } from '../apis';

const myCustomApiInstance = {
  // ...
};

await renderInTestApp(<MyComponent />, {
  apis: [
    mockApis.identity({ userEntityRef: 'user:default/guest' }),
    [myCustomApiRef, myCustomApiInstance],
  ],
});

renderTestApp으로

renderTestApp에도 같은 apis 옵션을 쓸 수 있어요. 확장이나 엔티티 페이지를 테스트할 때 흔히 쓰이죠.

import { renderTestApp, mockApis } from '@backstage/frontend-test-utils';
import { createTestEntityPage } from '@backstage/plugin-catalog-react/testUtils';

renderTestApp({
  extensions: [createTestEntityPage({ entity }), myEntityCard],
  apis: [mockApis.permission()],
});

TestApiProvider으로

renderInTestApp을 쓰지 않는 독립 렌더링 시나리오에서는 TestApiProvider 컴포넌트가 같은 apis 형식을 받아요.

import { render } from '@testing-library/react';
import { TestApiProvider, mockApis } from '@backstage/frontend-test-utils';

render(
  <TestApiProvider
    apis={[
      mockApis.identity({ userEntityRef: 'user:default/guest' }),
      mockApis.alert(),
    ]}
  >
    <MyComponent />
  </TestApiProvider>,
);

플러그인별 테스트 목

플러그인은 같은 패턴을 따르는 자신만의 목 API를 제공할 수 있어요. 예를 들어 @backstage/plugin-catalog-react는 /testUtils 진입점에서 catalogApiMock을 제공해요.

import { renderTestApp } from '@backstage/frontend-test-utils';
import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';

renderTestApp({
  extensions: [myExtension],
  apis: [catalogApiMock({ entities: [entity] })],
});

여러분만의 목 API 만들기

utility API를 노출하는 플러그인을 유지보수한다면, 내장 mockApis와 같은 function + .mock() 패턴을 따르는 목 유틸리티를 제공할 수 있어요.

Fake 인스턴스(실제 동작)에는 attachMockApiFactory를, 모든 메서드가 jest.fn()인 jest-목 .mock() 변형에는 createApiMock을 사용하세요. 전체 패턴은 다음과 같아요.

import {
  attachMockApiFactory,
  createApiMock,
  type ApiMock,
} from '@backstage/frontend-test-utils';
import { myApiRef, type MyApi } from '@internal/plugin-example-react';

// Fake instance with real behavior
export function myApiMock(options?: { greeting?: string }) {
  return attachMockApiFactory(myApiRef, {
    greet: async () => options?.greeting ?? 'Hello!',
  });
}

// Jest mock variant where all methods are jest.fn()
export namespace myApiMock {
  export const mock = createApiMock(myApiRef, () => ({
    greet: jest.fn(),
  }));
}

소비자는 그다음 핵심 목처럼 사용할 수 있어요.

// Fake with real behavior
await renderInTestApp(<MyComponent />, {
  apis: [myApiMock({ greeting: 'Hi there!' })],
});

// Jest mock for assertions
const api = myApiMock.mock({
  greet: async () => 'mocked',
});
await renderInTestApp(<MyComponent />, {
  apis: [api],
});
expect(api.greet).toHaveBeenCalledTimes(1);

사용 가능한 목 API

아래 표는 mockApis 네임스페이스를 통해 사용 가능한 모든 핵심 API를 나열해요.

| API | Fake instance | Notes | | mockApis.alert() | MockAlertApi | 알림을 수집함; getAlerts(), clearAlerts(), waitForAlert()를 가짐 | | mockApis.analytics() | MockAnalyticsApi | 이벤트를 수집함; getEvents()를 가짐 | | mockApis.config({ data }) | MockConfigApi | 평범한 JSON 객체에서 읽음 | | mockApis.discovery({ baseUrl }) | Inline | ${baseUrl}/api/${pluginId}를 반환함, 기본값은 http://example.com | | mockApis.error(options?) | MockErrorApi | 오류를 수집함; getErrors(), waitForError()를 가짐 | | mockApis.featureFlags(options?) | MockFeatureFlagsApi | 메모리 내 플래그 상태; getState(), setState(), clearState()를 가짐 | | mockApis.fetch(options?) | MockFetchApi | 네이티브 fetch를 감쌈; identity 주입과 플러그인 프로토콜 지원 | | mockApis.identity(options?) | Inline | 사용자 ref, ownership, token, profile 구성을 지원함 | | mockApis.permission(options?) | MockPermissionApi | 기본값은 ALLOW; 핸들러 함수를 받음 | | mockApis.storage({ data }) | MockStorageApi | 버킷 지원이 있는 메모리 내 저장 | | mockApis.translation() | MockTranslationApi | translation refs에서 기본 메시지를 반환하는 통과(passthrough) |

이 각각은 위에서 설명한 대로 jest 목을 반환하는 .mock() 변형도 가져요.

더 알아보기 (Learn more)