Open35

React テストの調査

西川信行西川信行

以下のライブラリが必要っぽい。

enzymeとJestは別物かな。

jest
ts-jest
react-test-renderer
enzyme enzyme-adapter-react-16
enzyme-to-json
@types/jest
@types/react-test-renderer
@types/enzyme-adapter-react-16
西川信行西川信行

今回使うReactは17系だから、react-16からreact-17に変更した方が良いのかなあ。
と思ったけど、enzyme-adapter-react-17にすると普通にバグる。まだenzyme側で対応して無いっぽいね。

西川信行西川信行

チュートリアルの内容を適当にまとめていくぅ。

npmで以下のようにインストール

npm install --save-dev jest

また、sum.jsファイルを以下のように作成します。

sum.js
function sum(a, b) {
  return a + b;
}
module.exports = sum;

その後、sum.test.jsというファイルを作成しましょう。

sum.test.js
const sum = require('./sum');

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

また、package.jsonに以下のようにnpm scriptsを追加します。

{
  "scripts": {
    "test": "jest"
  }
}

npm run testコマンドを実行すると、以下のようになりました。

> next-template@0.1.0 test
> jest

 PASS  ./sum.test.js
  ✓ adds 1 + 2 to equal 3 (1 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.167 s, estimated 1 s
西川信行西川信行
function.js
const functions = {
  add: (num1, num2) => num1 + num2,
}

module.exports = functions
function.test.js
const functions = require("./functions")

test("Adds 2 + 2 to equal 4", () => {
  expect(functions.add(2, 2)).toBe(4);
})
西川信行西川信行
function.test.js
test("Adds 2 + 2 to Not equal 5", () => {
  expect(functions.add(2, 2)).not.toBe(5);
})
西川信行西川信行
function.js
const functions = {
  add: (num1, num2) => num1 + num2,
  isNull: () => null
}

module.exports = functions
function.test.js

test("Should be null", () => {
  expect(functions.isNull()).toBeNull();
})
西川信行西川信行
function.js
const functions = {
  add: (num1, num2) => num1 + num2,
  isNull: () => null,
  checkValue: (x) => x
}

module.exports = functions
function.test.js
test("Should be falsy", () => {
  expect(functions.checkValue(null)).toBeFalsy();
})
西川信行西川信行
function.js
const functions = {
  add: (num1, num2) => num1 + num2,
  isNull: () => null,
  checkValue: (x) => x
}

module.exports = functions
function.test.js
test("Should be falsy", () => {
  expect(functions.checkValue(false)).toBeFalsy();
})
西川信行西川信行

これは通らない

function.js
const functions = {
  add: (num1, num2) => num1 + num2,
  isNull: () => null,
  checkValue: (x) => x
}

module.exports = functions
function.test.js
test("Should be falsy", () => {
  expect(functions.checkValue(2)).toBeFalsy();
})
西川信行西川信行

オブジェクトが一致していることをテスト。オブジェクトの一致にはtoEqualを使用する。

function.js
const functions = {
  add: (num1, num2) => num1 + num2,
  isNull: () => null,
  checkValue: (x) => x,
  createUser: () => {
    const user = { firstName: "Brad" }
    user["lastName"] = "Traversy"
    return user;
  },
}

module.exports = functions
function.test.js
test("User should be Brad Traversy object", () => {
  expect(functions.createUser()).toEqual({ firstName: "Brad", lastName: "Traversy" })
})
西川信行西川信行

数値が小さいことをテスト。

function.test.js
test("Should be under 1600", () => {
  const load1 = 800
  const load2 = 700
  expect(load1 + load2).toBeLessThan(1600)
})
西川信行西川信行

正規表現でテスト。

失敗するテスト

function.test.js
test('There is no I in team', () => {
  expect("teamI").not.toMatch(/I/)
})

成功するテスト

function.test.js
test('There is no I in team', () => {
  expect("teami").not.toMatch(/I/)
})
西川信行西川信行

ケースインセンスティブ(大文字と小文字を区別しない)正規表現パターンにすると失敗する。

function.test.js
test('There is no I in team', () => {
  expect("teami").not.toMatch(/I/i)
})
西川信行西川信行

配列があるリテラルを含むかどうかをテスト。

function.test.js
test("Admin should be in usernames", () => {
  usernames = ["john", "karen", "admin"]
  expect(usernames).toContain("admin")
})
西川信行西川信行

非同期処理のテストをしていくぅ。

function.js
const { default: axios } = require("axios")

const functions = {
  add: (num1, num2) => num1 + num2,
  isNull: () => null,
  checkValue: (x) => x,
  createUser: () => {
    const user = { firstName: "Brad" }
    user["lastName"] = "Traversy"
    return user
  },
  fetchUser: () =>
    axios
      .get("https://jsonplaceholder.typicode.com/users/1")
      .then((res) => res.data)
      .catch((err) => "error")
}

module.exports = functions
function.test.js
test("User fetched name should be Leanne Graham", () => {
  expect.assertions(1)
  return functions.fetchUser().then(data => {
    expect(data.name).toEqual("Leanne Graham")
  })
})

非同期処理のテストは、最初にexpect.assertions(n)をすることで、n回のアサーションが起きることを想定する?異常系のときのみ使うっぽい。

returnでPromiseを返せば良い?

以下の記事に目を通そうかな。

https://qiita.com/rikegami/items/178ed17982b13535ad59#promise-1

西川信行西川信行

assertionsをコメントアウトしても普通に通る。

function.js
const { default: axios } = require("axios")

const functions = {
  add: (num1, num2) => num1 + num2,
  isNull: () => null,
  checkValue: (x) => x,
  createUser: () => {
    const user = { firstName: "Brad" }
    user["lastName"] = "Traversy"
    return user
  },
  fetchUser: () =>
    axios
      .get("https://jsonplaceholder.typicode.com/users/1")
      .then((res) => res.data)
      .catch((err) => "error")
}

module.exports = functions
function.test.js

test("User fetched name should be Leanne Graham", () => {
  // expect.assertions(1)
  return functions.fetchUser().then(data => {
    expect(data.name).toEqual("Leanne Graham")
  })
})
西川信行西川信行

returnを消すと失敗する。

function.js
const { default: axios } = require("axios")

const functions = {
  add: (num1, num2) => num1 + num2,
  isNull: () => null,
  checkValue: (x) => x,
  createUser: () => {
    const user = { firstName: "Brad" }
    user["lastName"] = "Traversy"
    return user
  },
  fetchUser: () =>
    axios
      .get("https://jsonplaceholder.typicode.com/users/1")
      .then((res) => res.data)
      .catch((err) => "error")
}

module.exports = functions
function.test.js
test("User fetched name should be Leanne Graham", () => {
  expect.assertions(1)
  functions.fetchUser().then(data => {
    expect(data.name).toEqual("Leanne Graham")
  })
})
西川信行西川信行

async / awaitを使ってもテストが書けるぞい。

こっちの方が分かりやすいかな。

function.js
const { default: axios } = require("axios")

const functions = {
  add: (num1, num2) => num1 + num2,
  isNull: () => null,
  checkValue: (x) => x,
  createUser: () => {
    const user = { firstName: "Brad" }
    user["lastName"] = "Traversy"
    return user
  },
  fetchUser: () =>
    axios
      .get("https://jsonplaceholder.typicode.com/users/1")
      .then((res) => res.data)
      .catch((err) => "error")
}

module.exports = functions
function.test.js
test("User fetched name should be Leanne Graham", async () => {
  expect.assertions(1)
  const data = await functions.fetchUser()
  expect(data.name).toEqual("Leanne Graham")
})
西川信行西川信行

真面目な感じのテストコード。こんな感じに書くんだね。

reversestring.js
const reverseString = (str) => str.split("").reverse().join("")

module.exports = reverseString;
reversestring.test.js
const reverseString = require("./reversestring")

test("reverseString function exists", () => {
  expect(reverseString).toBeDefined()
})

test("String reverses", () => {
  expect(reverseString("hello")).toEqual("olleh")
})
西川信行西川信行

ちょっと改造。

reversestring.js
const reverseString = (str) => str.toLowerCase().split("").reverse().join("")

module.exports = reverseString
reversestring.test.js
const reverseString = require("./reversestring")

test("reverseString function exists", () => {
  expect(reverseString).toBeDefined()
})

test("String reverses", () => {
  expect(reverseString("hello")).toEqual("olleh")
})

test("String reverses with uppercase", () => {
  expect(reverseString("Hello")).toEqual("olleh")
})
西川信行西川信行

新しい関数を定義してテスト。

chunk.js
const chunkArray = (arr, len) => {
  const chunkedArr = []
  arr.forEach((val) => {
    const last = chunkedArr[chunkedArr.length - 1]

    if (!last || last.length === len) {
      chunkedArr.push([val])
    } else {
      last.push(val)
    }
  })

  return chunkedArr
}

module.exports = chunkArray
chunk.test.js
const chunkArray = require("./chunk")

test("chunkArray function exists", () => {
  expect(chunkArray).toBeDefined()
})

test("Chunk an array of 10 values with length of 2", () => {
  const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  const len = 2
  const chunkedArr = chunkArray(numbers, len)

  expect(chunkedArr).toEqual([
    [1, 2],
    [3, 4],
    [5, 6],
    [7, 8],
    [9, 10],
  ])
})
西川信行西川信行

こんなのでもOK

chunk.js
const chunkArray = (arr, len) => {
  const chunkedArr = []
  arr.forEach((val) => {
    const last = chunkedArr[chunkedArr.length - 1]

    if (!last || last.length === len) {
      chunkedArr.push([val])
    } else {
      last.push(val)
    }
  })

  return chunkedArr
}

module.exports = chunkArray
chunk.test.js
test("Chunk an array of 3 values with length of 1", () => {
  const numbers = [1, 2, 3]
  const len = 1
  const chunkedArr = chunkArray(numbers, len)

  expect(chunkedArr).toEqual([[1], [2], [3]])
})
西川信行西川信行

アナグラムをテストしていくぅ。

anagram.js
function isAnagram(str1, str2) {
  return formatStr(str1) === formatStr(str2)
}

function formatStr(str) {
  return str.replace(/[^/w]/g, "").toLowerCase().split("").sort().join("")
}

module.exports = isAnagram
anagram.test.js
const isAnagram = require("./anagram")

test("isAnagram function exists", () => {
  expect(typeof isAnagram).toEqual("function")
})

test('"cinema" is an anagram of "iceman"', () => {
  expect(isAnagram("cinema", "iceman")).toBeTruthy()
})

test('"Dormitory" is an anagram of "dirty room##"', () => {
  expect(isAnagram("Dormitory", "dirty room##")).toBeTruthy()
})
西川信行西川信行

各々のテストの前と後に処理を仕込みたいときは、beforeEachafterEachを使用すればOK。

データベースからデータの取得を行い、テスト終了後はデータベースとの接続を切断するときとかに便利かも。

beforeEach(() => initDatabase())
afterEach(() => closeDatabase())

const initDatabase = () => console.log('Detabase Initialized...')
const closeDatabase = () => console.log('Database Closed')
西川信行西川信行

すべてのテストの前に一度だけ処理をして、すべてのテストの後に一度だけ処理をしたい場合はbeforeAllafterAllを使用すればOK。

beforeAll(() => initDatabase())
afterAll(() => closeDatabase())

const initDatabase = () => console.log('Detabase Initialized...')
const closeDatabase = () => console.log('Database Closed')
beforeAll(() => initDatabase())
afterAll(() => closeDatabase())

const initDatabase = () => console.log('Detabase Initialized...')
const closeDatabase = () => console.log('Database Closed')
西川信行西川信行

describeはいくつかの関連するテストをまとめたブロックを作成する。

const nameCheck = () => console.log('Checking Name ...')

describe('Checking Names', () => {
  beforeEach(() => nameCheck())

  test('User is Jeff', () => {
    const user = "Jeff"
    expect(user).toBe('Jeff')
  })
  test('User is Karen', () => {
    const user = "Karen"
    expect(user).toBe('Karen')
  })
})
西川信行西川信行

package.jsonに以下を追加。

"scripts": {
    "test": "jest",
    "testwatch": "jest --watchAll"
  }

ずっとテストしてくれるようになるっぽいね。