백엔드 플러그인 및 모듈 테스트
백엔드 플러그인과 모듈을 테스트하는 유틸리티는 @backstage/backend-test-utils에서 사용할 수 있어요.
출처: 문서
본문
백엔드 플러그인과 모듈을 테스트하는 유틸리티는 @backstage/backend-test-utils에서 사용할 수 있어요. 이 섹션에서는 그 기능들을 설명해요.
백엔드 플러그인 및 모듈 테스트
백엔드 플러그인과 모듈 테스트를 용이하게 하기 위해 @backstage/backend-test-utils 패키지는 다수의 mock 서비스로 완성된 전체 백엔드 하네스를 시작하는 startTestBackend 함수를 제공해요. 그런 다음 테스트 실행을 위해 동작을 조정해야 하는 서비스에 대한 재정의를 제공할 수 있어요. 이 함수는 테스트의 대상인 여러 기능(백엔드 플러그인과 모듈을 위한 집합 용어)도 받아요.
이 함수는 HTTP 라우터 서비스 API로 라우트를 등록하는 플러그인의 실제 REST 서비스 표면을 쉽게 테스트하기 위해 supertest 같은 것과 함께 사용할 수 있는 HTTP 서버 인스턴스를 반환해요.
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';import request from 'supertest';import { myPlugin } from './plugin.ts';describe('myPlugin', () => { it('can serve values from config', async () => { const fakeConfig = { myPlugin: { value: 7 } }; const mockLogger = mockServices.logger.mock(); const { server } = await startTestBackend({ features: [ myPlugin(), mockServices.rootConfig.factory({ data: fakeConfig }), mockLogger.factory, ], }); const response = await request(server).get('/api/example/get-value'); expect(response.status).toBe(200); expect(response.body).toEqual({ value: 7 }); expect(mockLogger.info).toHaveBeenCalledWith('Starting myPlugin'); });});
이 예시는 mock 서비스 팩토리에 접근하고 옵션을 전달해 기본 mock 서비스를 재정의하는 방법을 보여줘요.
반환된 서버에는 동적으로 바인딩된 리슨 포트를 반환하는 port() 메서드도 있어요. 이를 사용해 실행 중인 테스트 서비스와 더 저수준의 네트워크 상호 작용을 수행할 수 있어요.
mock 서비스
@backstage/backend-test-utils의 mockServices 객체는 플러그인과 서비스 간 상호 작용을 검증하는 데 사용할 수 있는 모든 핵심 서비스에 대한 서비스 팩토리 함수와 mocks를 제공해요.
모든 mock 서비스는 대부분의 테스트에 충분한 팩토리 함수를 제공해요. 예시는 다음과 같아요:
const fakeConfig = { myPlugin: { value: 7 } };const { server } = await startTestBackend({ features: [ // Will provide access to the default urlReaders automatically. mockServices.urlReader.factory(), // Some factories accept options, in this example we provide some fake config. mockServices.rootConfig.factory({ data: fakeConfig }), ],});
서비스 구현을 mock해 상호 작용을 검증하고 싶은 상황이 있을 수 있어요. 그런 경우 mock 함수를 사용해 상호 작용할 수 있는 mock 객체를 얻을 수 있어요. 예시는 다음과 같아요:
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';import { myPlugin } from './plugin.ts';describe('myPlugin', () => { it('should call use UrlReader', async () => { const mockReader = mockServices.urlReader.mock(); await startTestBackend({ features: [myPlugin(), mockReader], }); expect(mockReader.readUrl).toHaveBeenCalledWith('https://backstage.io'); }); it('should call use UrlReader again', async () => { const partialImpl = jest.fn(); await startTestBackend({ features: [ myPlugin(), // You could also supply partial implementations to the mock function. mockServices.urlReader.mock({ readUrl: partialImpl }), ], }); expect(partialImpl).toHaveBeenCalledWith('https://backstage.io'); });});
사용 가능한 서비스:
-
auth -
cache -
database -
discovery -
events -
httpAuth -
httpRouter -
lifecycle -
logger -
permissions -
rootConfig -
rootHealth -
rootHttpRouter -
rootLifecycle -
rootLogger -
scheduler -
urlReader -
userInfo
원격 서비스 상호 작용 테스트
백엔드 플러그인이나 서비스가 HTTP 호출로 외부 서비스와 상호 작용한다면, msw 패키지를 활용해 실제 발신 요청을 가로채고 mock 응답을 반환할 것을 권장해요. 이렇게 하면 로컬 클라이언트가 아니라 원격 서비스를 스텁(stub)할 수 있어 더 철저하고 견고한 테스트가 돼요. 작동 방식에 대해 더 자세히는 그들의 문서에서 읽을 수 있어요.
@backstage/backend-test-utils 패키지는 registerMswTestHooks 함수를 내보내며, 이 함수는 msw 인스턴스를 설정하고 해체하기 위해 올바른 jest 수명 주기 훅이 호출되도록 보장하고, mock 규칙 중 하나와 일치하지 않는 요청을 완전히 거부하는 옵션을 활성화해요. 이렇게 하면 테스트가 테스트에서 프로덕션으로 트래픽을 실수로 누출하지 못하게 해요.
예시:
import { registerMswTestHooks } from '@backstage/backend-test-utils';import { http, HttpResponse } from 'msw';import { setupServer } from 'msw/node';describe('read from remote', () => { const worker = setupServer(); registerMswTestHooks(worker); it('should auth and read successfully', async () => { expect.assertions(1); worker.use( http.get('https://remote-server.com/api/v3/foo', ({ request }) => { expect(request.headers.get('authorization')).toBe('Bearer fake'); return HttpResponse.json({ value: 7 }, { status: 200 }); }), ); // exercise your plugin or service as usual, with real clients });});
데이터베이스 상호 작용 테스트
@backstage/backend-test-utils 패키지는 플러그인의 데이터베이스 상호 작용 테스트 기능(연결할 실제 데이터베이스 엔진이 있는 testcontainers 기반 Docker 이미지를 띄우는 것 포함)을 포함해요.
이러한 테스트의 기본 설정은 다음과 같을 수 있어요:
// MyDatabaseClass.test.tsimport { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils';import { MyDatabaseClass, type FooTableRow } from './MyDatabaseClass';describe('MyDatabaseClass', () => { // Change this to the set of constants that you actually actively intend to // support. This create call must be made inside a describe block. Make sure // to create only one TestDatabases instance per file, since spinning up // "physical" databases to test against is much costlier than creating the // "logical" databases within them that the individual tests use. const databases = TestDatabases.create({ ids: ['POSTGRES_18', 'POSTGRES_14', 'SQLITE_3', 'MYSQL_8'], }); // Just an example of how to conveniently bundle up the setup code async function createSubject(databaseId: TestDatabaseId) { const knex = await databases.init(databaseId); const subject = new MyDatabaseClass({ database: knex }); await subject.runMigrations(); return { knex, subject }; } describe('foo', () => { // Easily run the exact same test onto all supported databases it.each(databases.eachSupportedId())( 'should run foo on %p', async databaseId => { const { knex, subject } = await createSubject(databaseId); // raw knex is available for underlying manipulation await knex<FooTableRow>('foo').insert({ value: 2 }); // drive your system under test as usual await expect(subject.foos()).resolves.toEqual([{ value: 2 }]); }, ); });});
테스트 데이터베이스 인스턴스를 백엔드 플러그인이나 서비스에 전달하려면 테스트 데이터베이스에 coreServices.database의 mock 인스턴스 형태로 제공할 수 있어요.
const { knex, subject } = await createSubject(databaseId);const { server } = await startTestBackend({ features: [myPlugin(), mockServices.database.factory({ knex })],});
로컬에서 실행할 때 테스트는 속도를 위해 SQLite에서만 실행돼요. CI 환경 변수가 설정되면 주어진 모든 데이터베이스 엔진이 사용돼요.
Docker 기반 데이터베이스 엔진을 원하지 않거나 사용할 수 없다면(예: CI 환경이 데이터베이스를 네이티브로 제공할 수 있는 경우), TestDatabases는 존재할 때 고려하는 환경 변수를 통한 커스텀 연결 문자열을 지원해요.
-
BACKSTAGE_TEST_DATABASE_POSTGRES17_CONNECTION_STRING -
BACKSTAGE_TEST_DATABASE_POSTGRES13_CONNECTION_STRING -
BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING
서비스 팩토리 테스트
서비스 팩토리 테스트를 용이하게 하기 위해 @backstage/backend-test-utils 패키지는 통제된 컨텍스트에서 서비스를 인스턴스화할 수 있게 하는 ServiceFactoryTester 헬퍼를 제공해요.
다음 예시는 rootConfig 서비스의 mock 구현도 제공하는 서비스 팩토리를 테스트하는 방법을 보여줘요.
import { mockServices, ServiceFactoryTester,} from '@backstage/backend-test-utils';import { myServiceFactory } from './myServiceFactory.ts';describe('myServiceFactory', () => { it('should provide value', async () => { const fakeConfig = { myConfiguredValue: 7 }; const tester = ServiceFactoryTester.from(myServiceFactory, { dependencies: [mockServices.rootConfig.factory({ data: fakeConfig })], }); const myService = await tester.get('test-plugin'); expect(myService.getValue()).toBe(7); });});
서비스 팩토리 테스터는 또한 기본적으로 대부분의 핵심 서비스에 대한 mock 구현을 제공해요.