> For the complete documentation index, see [llms.txt](https://docs.decentraland.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.decentraland.org/contributor/contributor-ko/contributor-guides/testing-standards/testing-services-wkc.md).

# 서비스 테스트 (WKC)

잘 알려진 컴포넌트 서비스는 다음을 기반으로 구축된 서버입니다. [잘 알려진 컴포넌트](https://well-known-components.github.io/documentation/) 아키텍처로, 서비스의 각 부분이 모듈화되어 컴포넌트에 캡슐화되어 있습니다. 이러한 컴포넌트는 쉽게 교체할 수 있다는 점과 함께, 생성될 때 모든 의존성이 주입되므로 단위 테스트하기도 쉽습니다.

이러한 서비스의 테스트는 반드시 두 가지 서로 다른 유형의 테스트를 사용해 수행해야 합니다, **단위** 및 **통합**. 로직을 포함하는 컴포넌트는 단위 테스트로 테스트해야 하며, 주로 외부 서비스와 상호작용하는 컴포넌트는 통합 테스트로 테스트해야 합니다. 즉, 로직은 없고 대부분 데이터베이스 쿼리만 포함하는 데이터베이스 어댑터 컴포넌트는 반드시 통합적으로 테스트해야 합니다.

## 잘 알려진 컴포넌트 컴포넌트의 단위 테스트

모든 컴포넌트는 반드시 단위 테스트되어야 하며, 테스트 작성 방법에 대해 여기서 정의된 섹션을 따라 작성되어야 합니다.

이러한 컴포넌트가 어떻게 테스트되는지 보여주기 위해, 다음은 메서드 하나와 의존성 두 개만 가진 간단한 컴포넌트입니다, **settings** 및 **friends**:

```tsx
// 음성 컴포넌트

export function createVoiceComponent(dependencies: Pick<AppComponents, 'settings' | 'friends'>) {
	const { settings, friends } = dependencies

  async function canCallEachOther(caller: string, callee: string): Promise<boolean> {
	  const [callerSettings, calleeSetting] = Promise.all([settings.getPrivacySetting(caller), settings.getPrivacySetting(callee)])
	  const areFriends = await friends.areUsersFriends(caller, callee)
		return areFriends || (callerSettings !== Settings.ONLY_FRIENDS && calleeSettings !== Settings.ONLY_FRIENDS)
  }
  
  return {
	  createVoiceChat
  }
}
```

이러한 컴포넌트를 테스트하려면 적절한 모의 객체를 만들어야 합니다. 즉, 테스트할 컴포넌트와 인터페이스할 수 있도록 올바른 타입을 가지며, 다양한 종류의 테스트를 만들 수 있도록 유연해야 합니다. 이러한 모의 객체는 테스트 간에도 변경되지 않아야 하며, 각 테스트가 다른 테스트들로부터 분리된 자체 컨텍스트에서 실행된다는 아이디어를 보존해야 합니다.

이러한 모의 객체는 컴포넌트 모의 생성 함수로 생성해야 하며, 이름은 create**ComponentName**MockedComponent로 지어야 합니다. 위에서 만든 컴포넌트를 예로 들면, 다음은 그것들이 어떻게 보여야 하는지에 대한 예시입니다:

```tsx
// /test/mocks/settings.ts 파일에 배치
export function createSettingsMockedComponent(overrides?: Partial<jest.Mocked<ISettingsComponent>>): jest.Mocked<ISettingsComponent> {
  return {
    getPrivacySettings: overrides?.getPrivacySettings ?? jest.fn()
  }
}

// test/mocks/friends.ts 파일에 배치
export function createFriendsMockedComponent(overrides?: Partial<jest.Mocked<IFriendsComponent>>): jest.Mocked<IFriendsComponent> {
	return {
	  // 컴포넌트가 내보내는 어떤 메서드든 동작을 정의할 수 있는 유연성을 가집니다.
	  areUsersFriends: overrides?.areUsersFriends ?? jest.fn(),
	  // 사용하지 않는 메서드는 overrides 매개변수에서 생략할 수 있어, 단순한 함수 모의 객체가 됩니다.
	  hasBlockedUser: overrides?.hasBlockedUser ?? jest.fn()
	}
}
```

이러한 모의 객체를 잘 배치된 `beforeEach`안에서 사용하면, 테스트 실행들 간에 모의 객체가 올바르게 분리되어 정의된다는 것을 확신할 수 있습니다. 다음은 이러한 모의 객체를 어떻게 사용할 수 있는지에 대한 예시입니다:

```tsx
// 나중에 모의를 변경할 수 있도록 컨텍스트에 모의 함수를 정의합니다.
let getPrivacySettingsMock: jest.MockedFn<ISettingsComponent['getPrivacySettings']>
let areUsersFriendsMock: jest.MockedFn<IFriendsComponent['areUsersFriends']>
let voice: IVoiceComponent

beforeEach({
  getPrivacySettingsMock = jest.fn()
  areUsersFriendsMock = jest.fn()
  // 모의 객체를 만듭니다
  const settings = createSettingsMockedComponent({ getPrivacySettings: getPrivacySettingsMock })
  // 테스트에서 사용될 메서드만 사용해 컴포넌트 모의 객체를 초기화합니다
  const friends = createFriendsMockedComponent({ areUsersFriends: areUsersFriendsMock })
  // 모의 객체를 사용해 테스트할 컴포넌트를 생성합니다
  voice = createVoiceComponent({ settings, friends })
})
```

모의 객체를 이렇게 정의하는 방식은 테스트 실행 간의 컨텍스트를 올바르게 분리할 수 있게 해줄 뿐만 아니라, 모의 함수가 이미 정의되어 있으므로 올바른 컨텍스트 초기화를 위한 기반도 마련해 줍니다. 각 컨텍스트를 만드는 것은 매우 쉽습니다:

```tsx
// 이전 코드 블록 뒤에 정의됩니다.

describe('두 사용자가 서로 통화할 수 있는지 확인할 때', () => {
	const calleeAddress = '0xd7D746d39D142b6bE752efd7626cE28F245a25D1'
	const callerAddress = '0x2e8b4De1230f827082202aa53d489A26163aace0'

  describe('그리고 수신자는 친구로부터의 통화만 허용할 때', () => {
    beforeEach(() => {
      // 해당 컨텍스트의 모의 객체를 정의하여, 수신자를 
      getPrivacySettingsMock.mockImplementation((address: string) => {
	      switch(address) {
	        case caleeAddress:
		        return Settings.ONLY_FRIENDS
		      case callerAddress:
			      return Settings.ALL
			    default:
				    throw new Error("잘못된 모의 객체")
	      }
      })
    })
    
    describe('그리고 수신자와 발신자가 친구일 때', () => {
	    beforeEach(() => {
		    areUsersFriendsMock.mockResolvedValueOnce(true)
	    })
	    
	    it('true로 resolve되어야 한다', () => {
		    return expect(voice.canCallEachOther(callerAddress, calleeAddress)).resolves.toBe(true)
	    })
    })
    
    describe('그리고 수신자와 발신자가 친구가 아닐 때', () =>{
		  beforeEach(() => {
		    areUsersFriendsMock.mockResolvedValueOnce(false)
	    })
	    
	    it('false로 resolve되어야 한다', () => {
		    return expect(voice.canCallEachOther(callerAddress, calleeAddress)).resolves.toBe(false)
	    })
    })
  })
  
  //... 다른 컨텍스트들
})
```

## 통합 테스트

모든 엔드포인트, WS RPC 호출, 작업(job) 또는 태스크는 반드시 통합적으로 테스트되어야 하며, 테스트 작성 방법에 대해 여기서 정의된 섹션을 따라 작성되어야 합니다. 통합 테스트는 다음 경로 아래에 배치해야 합니다. `test/integration` 디렉터리이며, 테스트하려는 진입점에 맞게 이름 지어야 합니다.

통합 테스트는 단위 테스트로는 테스트할 수 없는 특정 통합 조건(SQL 쿼리, Redis 작업 등)이나, 우리의 로직 컴포넌트와 프로토콜(HTTP, WS) 간의 통합을 테스트하는 데에만 제한되어야 합니다.

통합 테스트를 어떻게 작성해야 하는지 예시를 보여주기 위해, 사용자의 친구 목록을 가져오는 간단한 HTTP 요청을 테스트해 보겠습니다.

```tsx
export async function getFriendHandler(
  context: Pick<
    HandlerContextWithPath<'logs' | 'communities', '/v1/users/:user/friends'>,
    'url' | 'components' | 'params' | 'verification'
  >
): Promise<HTTPResponse<AggregatedCommunityWithMemberData>> {
  const {
    components: { friends },
    params: { id },
    verification
  } = context

  try {
    const userAddress = verification!.auth.toLowerCase()

    return {
      status: 200,
      body: {
        data: await friends.getFriends(userAddress)
      }
    }
  } catch (error) {
  
	  if (error instanceof InvalidFriendshipRequest) {
			throw InvalidRequest(isErrorWithMessage(error) ? error.message : '알 수 없는 오류')
	  }

    return {
      status: 500,
      body: {
        message
      }
    }
  }
}
```

그리고 다음과 같은 friends 컴포넌트가 있습니다:

```tsx
async function getFriends(addres: string) {
	friendsDb.getFriends()
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.decentraland.org/contributor/contributor-ko/contributor-guides/testing-standards/testing-services-wkc.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
