Unit test cho MobX State Tree với Jest trong React Native

Jest

Jest là 1 thư viện testing, được tạo bởi Facebook và được viết bằng Javascript. Mục tiêu ban đầu được viết ra để phục vụ riêng cho ReactJs, thế nhưng vì tính đa dụng của nó nên đã trở thành thư viện testing cho ứng dụng viết bằng Javascript.
Jest có thể dùng để viết test cho UI component và cả logic của chức năng, như tiêu đề bài viết, mình chỉ nói về phần test chức năng.

Cài đặt

Từ phiên bản react native 0.38, khi tạo mới project react native đã có sẵn Jest rồi nên bạn không cần phải cài thêm bất cứ thứ gì. Nếu muốn biết thêm chi tiết, bạn có thể vào Jest tutorial React Native để đọc thêm.

Tạo project

Như những bài viết trước đã nói, ở đây mình dùng ignite-cli để tạo project mới(dĩ nhiên sẽ có Jest và MobX State Tree). Nếu bạn chưa đọc thì có thể ghé qua bài viết của mình.

$ npx ignite-cli new DemoJest

Chạy yarn test sẽ cho kết quả như sau

Tạo Model

Model là nơi thực hiện xử lý logic của App, bạn viết phần xử lý của chức năng ở đây. Thay vì tạo bằng tay, ignite-cli có lệnh để tạo ra các file cơ bản của 1 file Model:

$ ignite g model user

Thư mục sau khi tạo bằng lệnh trên

user
│── user.props.ts
│── user.test.props
└── user.ts

Dưới đây là ví dụ về Model User:

export const UserModel = types
  .model("User")
  .extend(withEnvironment)
  .props({
    loading: types.optional(types.boolean, false),
    data: types.optional(types.array(Data), []),
    error: types.maybeNull(types.string),
  })
  .actions((self) => ({
    setLoading: (loading: boolean) => {
      self.loading = loading
    },
    setError: (error: string) => {
      self.error = error
    },
    setData: (data: DataProps[] | []) => {
      self.data.replace(data)
    },
  }))
  .actions((self) => ({
    getListUsers: flow(function* () {
      self.setLoading(true)
      const result = yield self.environment.api.getListUser()
      if (result.kind === "ok") {
        self.setData(result?.data?.data)
      } else {
        self.setError(result.data)
      }
      self.setLoading(false)
    }),
  }))

Giải thích 1 chút về các hàm:

  • setLoading: param có kiểu là boolean. Khi được gọi đến sẽ thay đổi giá trị loading dựa vào param.
  • setError: param có kiểu là string. Khi được gọi đến sẽ thay đổi giá trị error dựa vào param.
  • setData: param có kiểu là 1 mảng DataProp(được định nghĩa trước đó) hoặc 1 mảng rỗng. Khi được gọi đến sẽ thay thế giá trị data dựa vào param.
  • getListUsers: hàm lấy danh sách user. Khi được gọi đến, hàm này hoạt động như sau:
    • Chạy hàm setLoading với param là true.
    • Gọi đến API để lấy dữ liệu từ server, nếu lấy được dữ liệu từ server thì gọi hàm setData với param là dữ liệu lấy được, còn có lỗi xảy ra thì gọi hàm setError với param là lỗi từ phía server trả về.
    • Chạy hàm setLoading với param là false.

Viết test

Trước khi viết test, cần khởi tạo Model bên trong hàm beforeEach với mock environment.

beforeEach(() => {
  const mockEnvironment = {
    api: {
      getListUser: jest.fn(),
    }
  }
  instance = UserModel.create({}, mockEnvironment)
})

Viết test cho action đơn giản trước:

Hàm test(name, function, timeout) nhận vào 3 tham số:

  • name: tên của test.
  • function: hàm chứa các kỳ vọng sau khi chạy test mong muốn đạt được.
  • timeout(optional): thời gian chờ (tính bằng mili giây) để chỉ định thời gian chờ trước khi hủy bỏ(mặc định là 5000ms).
test("test setLoading", () => {
  instance.setLoading(true)
  expect(instance.loading).toEqual(true)
})
  • expect(prop).toEqual(value): mong muốn prop có giá trị bằng với value

Tương tự, ta viết test cho 2 actions còn lại như sau:

test("test setError", () => {
  instance.setError("Error found")
  expect(instance.error).toEqual("Error found")
})
test("test setData", () => {
  instance.setData([])
  expect(instance.data).toEqual([])
})

Tiếp tục viết test cho trường hợp lấy dữ liệu từ server thành công:

const resultSuccess = {
  kind: "ok",
  data: {
    data: [],
    message: "",
    success: true,
  },
}

test("test getListUsers success", async () => {
  instance.environment.api.getListUser.mockReturnValue(Promise.resolve(resultSuccess))
  await instance.environment.api.getListUser(0)
  expect(instance.environment.api.getListUser).toHaveBeenCalledWith(0)
  expect(instance.data).toEqual(resultSuccess.data.data)
  expect(instance.loading).toEqual(false)
})
  • mockReturnValue(value): gán cho hàm được gọi trả về giá trị value
  • expect(function).toHaveBeenCalledWith(param): mong muốn hàm function được gọi với đối số param

Tương tự với viết test cho trường hợp lấy dữ liệu từ server không thành công

const resultFail = {
  kind: "fail",
  data: "",
}

test("test getListUsers fail", async () => {
  instance.environment.api.getListUser.mockReturnValue(Promise.resolve(resultFail))
  await instance.environment.api.getListUser(0)
  expect(instance.environment.api.getListUser).toHaveBeenCalledWith(0)
  expect(instance.error).toEqual("Error")
  expect(instance.loading).toEqual(false)
})

Cuối cùng là chạy yarn test sẽ đựợc

Tổng kết

Hy vọng qua bài viết này, bạn hiểu hơn về jest và có thể áp dụng nó trong project của mình.