프론트엔드 플러그인 테스트하기
프론트엔드 플러그인 테스트하기 (Testing Frontend Plugins)
프론트엔드 기능과 컴포넌트를 테스트하기 위한 유틸리티는 @backstage/frontend-test-utils에서 사용할 수 있어요.
출처: 문서
본문
프론트엔드 기능과 컴포넌트를 테스트하기 위한 유틸리티는 @backstage/frontend-test-utils에서 사용할 수 있어요.
React 컴포넌트 테스트하기
컴포넌트는 하나 이상의 확장에 사용될 수 있으며, 확장 환경과 독립적으로 테스트되어야 해요.
renderInTestApp 헬퍼를 사용해 주어진 컴포넌트를 Backstage 테스트 앱 안에서 렌더링하세요.
import { screen } from '@testing-library/react';import { renderInTestApp } from '@backstage/frontend-test-utils';import { EntityDetails } from './plugin';describe('Entity details component', () => { it('should render the entity name and owner', async () => { await renderInTestApp(<EntityDetails owner="tools" name="test" />); await expect( screen.findByText('The entity "test" is owned by "tools"'), ).resolves.toBeInTheDocument(); });});
컴포넌트가 사용하는 Utility API를 모의(mock)하려면 apis 옵션을 사용해 API 재정의를 renderInTestApp에 전달하세요. 모의 헬퍼는 @backstage/frontend-test-utils와 플러그인별 테스트 유틸리티에서 사용할 수 있어요. 사용 가능한 모의 API와 직접 만드는 방법에 대한 자세한 내용은 Utility API로 테스트하기를 참고하세요.
import { screen } from '@testing-library/react';import { renderInTestApp, mockApis } from '@backstage/frontend-test-utils';import { identityApiRef } from '@backstage/frontend-plugin-api';import { catalogApiRef } from '@backstage/plugin-catalog-react';import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';import { MyEntitiesList } from './plugin';describe('MyEntitiesList', () => { it('should render entities owned by the current user', async () => { await renderInTestApp(<MyEntitiesList />, { apis: [ [ identityApiRef, mockApis.identity({ userEntityRef: 'user:default/guest' }), ], [ catalogApiRef, catalogApiMock({ entities: [ { apiVersion: 'backstage.io/v1alpha1', kind: 'Component', metadata: { name: 'my-component' }, spec: { type: 'service', owner: 'user:default/guest' }, }, ], }), ], ], }); await expect( screen.findByText('my-component'), ).resolves.toBeInTheDocument(); });});
이 접근 방식은 앱 수준에서 API 재정의를 제공하며, 컴포넌트 트리 깊은 곳의 API에 의존하는 컴포넌트를 테스트할 때 유용해요.
TestApiProvider 컴포넌트는 renderInTestApp이나 다른 테스트 유틸리티를 사용하지 않는 독립형 렌더링 시나리오에서도 사용할 수 있어요. @backstage/plugin-catalog-react의 EntityProvider 같은 컨텍스트 제공자를 사용해 컴포넌트에 모의된 엔터티 컨텍스트를 제공할 수도 있어요.
확장 테스트하기
프론트엔드 확장 테스트를 용이하게 하기 위해 @backstage/frontend-test-utils 패키지는 여러 기본 기능을 갖춘 전체 프론트엔드 하니스를 시작하는 테스터 클래스를 제공해요. 그런 다음 테스트 실행에 맞게 조정해야 하는 동작의 확장에 대한 재정의를 제공할 수 있어요.
테스터는 여러 기능(프론트엔드 확장과 재정의)도 받아들여요. 이러한 기능이 어떻게 유용할 수 있는지에 대한 몇 가지 예시는 다음과 같아요.
단일 확장 (Single extension)
확장을 격리해 테스트하려면 createExtensionTester를 사용해 테스터 인스턴스를 만들고 확장이 출력하는 요소에 접근할 수 있어요. 이 요소는 평소대로 renderInTestApp으로 렌더링할 수 있어요.
import { screen } from '@testing-library/react';import { createExtensionTester } from '@backstage/frontend-test-utils';import { indexPageExtension } from './plugin';describe('Index page', () => { it('should render the index page', async () => { await renderInTestApp( createExtensionTester(indexPageExtension).reactElement(), ); expect(screen.getByText('Index Page')).toBeInTheDocument(); });});
apis 옵션을 사용해 createExtensionTester에 API 재정의를 직접 제공할 수도 있어요.
import { screen } from '@testing-library/react';import { createExtensionTester, mockApis, renderInTestApp,} from '@backstage/frontend-test-utils';import { identityApiRef } from '@backstage/frontend-plugin-api';import { indexPageExtension } from './plugin';describe('Index page', () => { it('should render with a custom identity', async () => { await renderInTestApp( createExtensionTester(indexPageExtension, { apis: [ [ identityApiRef, mockApis.identity({ userEntityRef: 'user:default/guest' }), ], ], }).reactElement(), ); expect(screen.getByText('Index Page')).toBeInTheDocument(); });});
.reactElement() 메서드는 확장 출력에서 coreExtensionData.reactElement 데이터를 찾는다는 점에 유의하세요. 그것이 존재하지 않고 확장이 테스트하고 싶은 다른 것을 출력한다면, .get(dataRef) 메서드를 사용해 출력 데이터에 접근할 수 있어요.
여러 확장 (Multiple extensions)
특히 입력을 테스트할 때 여러 확장을 함께 테스트해야 할 수도 있어요. 이 경우 .add(...) 메서드를 사용해 테스터 인스턴스에 확장을 더 추가할 수 있어요. 이 메서드는 확장 인스턴스의 구성을 제공하는 데 사용할 수 있는 선택적 옵션 객체를 두 번째 인자로 받기도 해요.
import { screen } from '@testing-library/react';import userEvent from '@testing-library/user-event';import { createExtensionTester } from '@backstage/frontend-test-utils';import { indexPageExtension, indexPageHeader } from './plugin';describe('Index page', async () => { it('should link to the index page with header', async () => { const tester = createExtensionTester(indexPageExtension) // Adding the header to be rendered on the index page .add(indexPageHeader); await renderInTestApp(tester.reactElement()); await expect(screen.findByText('Index page')).toBeInTheDocument(); await expect(screen.findByText('Index page header')).toBeInTheDocument(); expect( tester.query(indexPageHeader).get(headerDataRef), ).toMatchObject(/* ... */); });});
여러 확장을 테스트할 때 때로는 주요 테스트 대상이 아닌 다른 확장의 출력에 접근하고 싶을 수 있어요. .query(ext) 메서드를 사용해 테스터에 추가된 다른 확장을 조회할 수 있는데, createExtensionTester(...).add(ext)에서 사용한 확장을 전달하면 돼요.
구성 설정하기
확장이 구성될 수 있는 경우 구성 값을 전달해 이 기능을 테스트할 수 있어요.
import { screen } from '@testing-library/react';import userEvent from '@testing-library/user-event';import { createExtensionTester } from '@backstage/frontend-test-utils';import { indexPageExtension, detailsPageExtension } from './plugin';describe('Index page', () => { it('should accept a custom title via config', async () => { const tester = createExtensionTester(indexPageExtension, { // Extension configuration for the index page config: { title: 'Custom page' }, }).add(indexPageHeader, { // Extension configuration for the index page header config: { title: 'Custom page header' }, }); await renderInTestApp(tester.reactElement(), { // Global configuration for the app config: { app: { title: 'Custom app', }, }, }); await expect(screen.findByText('Custom app')).toBeInTheDocument(); await expect(screen.findByText('Custom page')).toBeInTheDocument(); await expect(screen.findByText('Custom page header')).toBeInTheDocument(); });});
엔터티 확장 테스트하기
@backstage/plugin-catalog-react/testUtils의 createTestEntityPage 유틸리티는 엔터티 카드와 콘텐츠 확장 테스트를 단순화해요. /에 마운트되는 테스트 페이지를 만들고 EntityProvider 컨텍스트를 제공하며 입력 리다이렉트를 통해 엔터티 확장을 선택해요.
import { screen } from '@testing-library/react';import { renderTestApp } from '@backstage/frontend-test-utils';import { createTestEntityPage } from '@backstage/plugin-catalog-react/testUtils';import { myEntityCard } from './plugin';describe('MyEntityCard', () => { it('should render for Component entities', async () => { const entity = { apiVersion: 'backstage.io/v1alpha1', kind: 'Component', metadata: { name: 'my-service' }, spec: { type: 'service', owner: 'team-a' }, }; renderTestApp({ extensions: [createTestEntityPage({ entity }), myEntityCard], }); expect(await screen.findByText('My Card Title')).toBeInTheDocument(); });});
엔터티 콘텐츠 확장은 정확히 같은 방식으로 테스트할 수 있어요. 카드 대신 콘텐츠 확장만 전달하면 돼요. 테스트 페이지는 확장에 정의된 엔터티 필터도 지원하므로, 다른 엔터티 종류를 제공해 필터 동작을 테스트할 수 있어요. 확장이 API에 의존한다면 renderTestApp의 apis 옵션을 사용해 모의 구현을 전달하거나, 콘텐츠 확장 옆에 API 확장을 직접 전달할 수 있어요.
EntityRefLinks나 useRelatedEntities를 사용하는 확장은 renderTestApp의 apis 옵션을 사용한 추가 API 모의가 필요할 수 있어요.
라우트 마운트하기 (Mounting routes)
컴포넌트나 확장이 useRouteRef를 사용해 다른 라우트로 가는 링크를 생성한다면, 테스트 환경에서 그 라우트들을 마운트해야 해요. renderInTestApp과 renderTestApp 모두 이 목적을 위해 mountedRoutes 옵션을 지원해요.
예를 들어 useRouteRef를 사용해 링크를 만드는 컴포넌트가 있다면:
import { useRouteRef } from '@backstage/frontend-plugin-api';import { detailsRouteRef } from './routes';export const MyComponent = () => { const detailsLink = useRouteRef(detailsRouteRef); return <a href={detailsLink()}>View details</a>;};
mountedRoutes 옵션을 사용해 라우트 참조를 경로에 마운트해 테스트할 수 있어요.
import { screen } from '@testing-library/react';import { renderInTestApp } from '@backstage/frontend-test-utils';import { detailsRouteRef } from './routes';import { MyComponent } from './MyComponent';describe('MyComponent', () => { it('should render a link to the plugin page', async () => { await renderInTestApp(<MyComponent />, { mountedRoutes: { '/my-plugin/details': detailsRouteRef, }, }); expect(await screen.findByText('View details')).toHaveAttribute( 'href', '/my-plugin/details', ); });});
확장 트리 스냅샷 (Extension tree snapshots)
ExtensionTester의 snapshot() 메서드는 해석된 확장 계층 구조의 트리 형태 표현을 반환하며, 테스트에서 확장 구조를 검증하기 위해 Jest의 toMatchInlineSnapshot()과 함께 사용하기 편리해요.
빠진 것이 있나요? (Missing something?)
문서에 다뤄야 한다고 생각하는 것이 있거나 테스트 유틸리티가 다루지 않는다고 생각하는 것이 있다면 Backstage 저장소에 이슈를 만들어 주세요. 기여도 언제든 환영해요!