Remix 앱 로더에 대한 첫 번째 단위 테스트 만들기

28637 단어
우리는 Seasoned에서 Remix 앱의 신뢰도와 유지 관리성을 높이기 위해 E2E와 단위 테스트를 혼합하여 사용하고 있습니다.

여기서 단계별로 로더를 단위 테스트하는 한 가지 방법을 보여드리겠습니다. 이 글을 쓰는 시점에는 Remix 코드가 있는 구성 요소를 테스트하는 표준 방법이 없습니다. 따라서 우리는 비즈니스 로직, 로더 및 작업을 개별적으로 테스트하고 있습니다.

이 문서에서는 인증 없는 샘플 테스트를 보여줍니다. 우리 자신의 인증은 뚜렷하고 큰 주제입니다. 테스트 인증 설정은 독자에게 연습으로 남겨둡니다 😬

E2E에 관심이 있다면 remix-forms repo에서 예제를 볼 수 있습니다.

우리가 만들고 있는 것



Remix 문서를 처음 따를 때 빌드한 the remix-jokes app을 사용하겠습니다. 당시에는 리믹스 스택이 없었기 때문에 새로 설치했습니다. 😅 이 앱은 단위 테스트를 실행하기 위해 Express 서버, Postgres DB 및 Jest를 사용합니다.

설정



경로 파일을 테스트합니다. 이러한 테스트 파일의 가장 좋은 위치는 경로 자체에 가깝습니다. 하지만 테스트 파일이 경로가 아니라고 Remix에 알려야 합니다. 그렇지 않으면 서버가 중단됩니다. Remix 구성에 ignoredRouteFiles를 추가하면 모두 좋습니다.

// remix.config.js
module.exports = {
  ...
  ignoredRouteFiles: ['.*', '**/__tests__/**'],
}


첫 번째 테스트 추가(및 코너 케이스 발견)



테스트해봅시다app/routes/jokes/$jokeId.tsx .

//app/routes/jokes/$jokeId.tsx

export const loader: LoaderFunction = async ({ request, params }) => {
  const userId = await getUserId(request)

  const joke = await db.joke.findUnique({
    where: { id: params.jokeId },
  })

  if (!joke) {
    throw new Response('What a joke! Not found.', {
      status: 404,
    })
  }
  const data: LoaderData = { joke, isOwner: joke.jokester_id === userId }
  return data
}


로더는 명명된 내보내기이므로 다른 기능으로 가져올 수 있습니다. 하지만 다시 어떻게 부릅니까? 😕

// app/routes/jokes/__tests__/jokeid.test.ts
import { loader } from '../$jokeId'

describe('loader', () => {
  it('first try', async () => {
    // What params do we need to call the loader? 
    expect(loader()).toEqual('foo') // does not work, of course
  })
})


구조에 타이프 스크립트! request , contextparams 3개의 매개변수가 필요합니다.
Request 클래스를 사용하기 위한 Jest 환경must be set to "node". 함수 호출이 작동하는지 확인하기 위해 더미 테스트를 추가해 보겠습니다.

// app/routes/jokes/__tests__/jokeid.test.ts
import { loader } from '../$jokeId'

describe('loader', () => {
  it('second try', async () => {
    const request = new Request('http://foo.ber')

    const response = await loader({ request, context: {}, params: {} })

    expect(true).toBe(true)
  })
})


💣💥

우리는 id 매개변수 없이 로더를 호출했는데 폭발했습니다. 이 경로에는 항상 ID가 있으므로 함수 호출이 유효하지 않기 때문에 이전에는 함수가 해당 사례를 처리하지 않았습니다. 아이디를 추가해보겠습니다.

// app/routes/jokes/__tests__/jokeid.test.ts
import { loader } from '../$jokeId'

describe('loader', () => {
  it('second try', async () => {
    const request = new Request('http://foo.ber')

    const response = await loader({ request, context: {}, params: { jokeId: 'foo' } })

    expect(true).toBe(true)
  })
})


오류가 변경되었으므로 진행 상황입니다.

Invalid prisma.joke.findUnique() invocation:


      Inconsistent column data: Error creating UUID, invalid length: expected one of [36, 32], found 3

jokeId 매개변수는 특정 길이를 가져야 합니다. 코드에서 해당 사례를 처리해 보겠습니다.

//app/routes/jokes/$jokeId.tsx
export const loader: LoaderFunction = async ({ request, params }) => {
  const userId = await getUserId(request)

  const jokeId = params.jokeId || ''

  if (![32, 36].includes(jokeId.length)) {
    throw new Response('Joke id must be 32 or 36 characters', { status: 400 })
  }

  ...
}


이제 첫 번째 실제 테스트를 실행할 수 있습니다.

// app/routes/jokes/__tests__/jokeid.test.ts
  describe('loader', () => {
  it('fails with an invalid id', async () => {
    const request = new Request('http://foo.ber')

    try {
      await loader({ request, context: {}, params: { jokeId: 'foo' } })
    } catch (error) {
      expect((error as Response).status).toEqual(400)
    }
    // Todo: assert loader has thrown
  })
})


하지만 주의 사항이 있습니다. 어떤 이유로 catch 블록에 속하지 않으면 이 테스트에 expect가 없고 녹색이 됩니다. 이를 처리하는 방법에는 여러 가지가 있으며 그 중 하나는 다음과 같습니다.

// app/routes/jokes/__tests__/jokeid.test.ts
...
  it('fails with an invalid id', async () => {
    const request = new Request('http://foo.bar')

    let result
    try {
      await loader({ request, context: {}, params: { jokeId: 'foo' } })
    } catch (error) {
      result = error
    }

    expect((result as Response).status).toEqual(400)
  })


예를 들어 result가 null이 아니라고 주장할 수도 있습니다. 그러나이 솔루션은 충분해 보입니다.

테스트 '찾을 수 없음'



위의 테스트와 매우 유사합니다. 가짜 임의 ID를 사용하면 트릭을 수행합니다.

// app/routes/jokes/__tests__/jokeid.test.ts
...
  it('returns 404 when joke is not found', async () => {
    const request = new Request('http://foo.bar')

    let result
    try {
      await loader({
        request,
        context: {},
        params: { jokeId: '49ed1af0-d122-4c56-ac8c-b7a5f033de88' },
      })
    } catch (error) {
      result = error
    }

    expect((result as Response).status).toEqual(404)
  })


행복한 길을 시험하다



이것은 데이터베이스에 무언가가 있는 한 간단합니다.

// app/routes/jokes/__tests__/jokeid.test.ts
...
  it('returns the joke when it is found', async () => {
    const request = new Request('http://foo.bar')

    const jokes = await db.joke.findMany({ take: 1 })
    const joke = jokes[0]
    const { id } = joke

    let result
    result = await loader({
      request,
      context: {},
      params: { jokeId: id },
    })

    expect(result).toEqual({ joke, isOwner: false })
}


물론 특정 DB 데이터에 의존하는 것은 프로젝트가 커짐에 따라 문제가 발생할 수 있습니다. Prisma docs은 클라이언트를 조롱할 것을 권장합니다.

전체 테스트 파일:

// app/routes/jokes/__tests__/jokeid.test.ts
import { loader } from '../$jokeId'
import { db } from '~/utils/db.server'

describe('loader', () => {
  it('fails with an invalid id', async () => {
    const request = new Request('http://foo.bar')

    let result
    try {
      await loader({
        request,
        context: {},
        params: { jokeId: 'foo' },
      })
    } catch (error) {
      result = error
    }

    expect((result as Response).status).toEqual(400)
  })

  it('returns not found when joke is not found', async () => {
    const request = new Request('http://foo.bar')

    let result
    try {
      await loader({
        request,
        context: {},
        params: { jokeId: '49ed1af0-d122-4c56-ac8c-b7a5f033de88' },
      })
    } catch (error) {
      result = error
    }

    expect((result as Response).status).toEqual(404)
  })

  it('returns the joke when it is found', async () => {
    const request = new Request('http://foo.bar')

    const jokes = await db.joke.findMany({ take: 1 })
    const joke = jokes[0]
    const { id } = joke

    let result
    result = await loader({
      request,
      context: {},
      params: { jokeId: id },
    })

    expect(result).toEqual({ joke, isOwner: false })
  })
})


우리는 결국 다른 접근 방식을 사용하게 될 것입니다. 각 테스트 실행에 대해 재설정되는 테스트 데이터베이스를 만드는 것입니다. 그러나 그것은 향후 게시물의 주제입니다!

첫 로더 테스트 작성이 즐거웠기를 바랍니다. 😊

좋은 웹페이지 즐겨찾기