Abstraction of Testing
Tags: javascript, testing, beginners, codenewbie
Published: 2021-09-26 21:05:47

Sebelum kita jauh belajar mengenai testing, mari kita buat contoh sederhana dari 0, agar lebih mudah untuk memahami.

Well, sesuai namanya `testing`, digunakan untuk memeriksa kode kita,
dan sesegera mungkin mengidentifikasi di mana letak kesalahannya.

Dari kode berikut kita menemukan error

```js
if (result !== expected) throw new Error(`${result} is not equal to ${expected}`)
                         ^

Error: -4 is not equal to 10
    at Object.<anonymous> (/simple.js:6:32)
```

## Separate code & testing

Alih alih menempatkan target testing & testing code kita dalam 1 file, mari kita pisahkan terlebih dahulu.

Kode kita dalam `math.js`
```js
const sum = (a, b) => a - b;
const subtract = (a, b) => a - b;

module.exports = {
  sum,
  subtract,
};
```

Dan testing code kita dalam `assertion-library.js`
```js
const { sum, subtract } = require("./math");

let result, expected;

result = sum(3, 7);
expected = 10;
if (result !== expected)
  throw new Error(`${result} is not equal to ${expected}`);

result = subtract(7, 3);
expected = 4;
if (result !== expected)
  throw new Error(`${result} isn not equal to ${expected}`);
```

## Refactor Assertion Library

Pada testing file, kita melihat kode yang berulang.
```js
if (result !== expected)
  throw new Error(`${result} is not equal to ${expected}`);
```

Untuk satu/dua testing mungkin tidak masalah.
Namun akan jadi masalah untuk banyak testing.

Mari kita sederhanakan
```js
// assetion-library.js
const { sum, subtract } = require("./math");

let result, expected;

result = sum(3, 7);
expected = 10;
expect(result).toEqual(expected);

result = subtract(7, 3);
expected = 4;
expect(result).toEqual(expected);

function expect(actual) {
  return {
    toEqual(expected) {
      if (actual !== expected)
        throw new Error(`${actual} is not equal to ${expected}`);
    },
  };
}
```
Anyway, ini juga bertujuan untuk membuat pesan error lebih konsisten.
Dan tentu saja kita dapat menambah fungsi selain `toEqual`,
seperti `toBe, toBeGreaterThan` dan lainnya

## Mengatasi "failed test menghentikan eksekusi kode"

Sadar atau tidak,
pada kode di atas, apabila kita gagal test, maka test berikutnya tidak dijalankan,
dengan kata lain eksekusi kode berhenti.

Tentu saja, karena kita menggunakan `throw new Error()`

Mari kita ubah `assertion-library` untuk mengatasi hal ini.

```js
function test(title, callback) {
  try {
    callback();
    console.log(`✅  ${title}`);
  } catch (error) {
    console.error(`❌ ${title}`);
    console.log(error);
  }
}
```

- `try catch` di atas berfungsi untuk mengatasi `throw new Error`, dengan begitu, failed test tidak akan menhentikan eksekusi testing file.
- berfungsi juga untuk merapikan pesan yang tampil kepada kita

Adapun implementasinya
```js
test("sum function", () => {
  const result = sum(3, 7);
  const expected = 10;
  expect(result).toEqual(expected);
});
```

Maka pesan yang akan kita terima
```js
❌ sum function
Error: -4 is not equal to 10
    at Object.toEqual (/assertion-library.js:29:15)
    ...
✅  subtract function
```

File penuhnya sebagai berikut
```js
// assertion-library.js
const { sum, subtract } = require("./math");

test("sum function", () => {
  const result = sum(3, 7);
  const expected = 10;
  expect(result).toEqual(expected);
});

test("subtract function", () => {
  const result = subtract(7, 3);
  const expected = 4;
  expect(result).toEqual(expected);
});

function test(title, callback) {
  try {
    callback();
    console.log(`✅  ${title}`);
  } catch (error) {
    console.error(`❌ ${title}`);
    console.log(error);
  }
}

function expect(actual) {
  return {
    toEqual(expected) {
      if (actual !== expected)
        throw new Error(`${actual} is not equal to ${expected}`);
    },
  };
}
```

## Support Async Function to Test

Sejauh ini library kita bekerja,
namun apakah juga bekerja untuk fungsi dengan kondisi spesial seperti promise/async function?

mari kita buat fungsi & test-nya
```js
// math.js
...
const sumAsync = (a, b) => Promise.resolve(sum(a, b));

module.exports = {
  ...
  sumAsync,
};
```

```js
// assertion-library.js
const { ... sumAsync } = require("./math");

...
test("sum async", async () => {
  const result = await sumAsync(3, 7);
  const expected = 10;
  expect(result).toEqual(expected);
})
...
```

maka pesan yang akan kita dapat adalah
```js
...
✅  sum async
/assertion-library.js:35
        throw new Error(`${actual} is not equal to ${expected}`);
              ^

Error: -4 is not equal to 10
    at Object.toEqual (/assertion-library.js:35:15)
    at /assertion-library.js:18:18
    ...
```
Pengecekan berhasil, tapi menunjukkan pesan error? aneh bukan?

Karenanya kita perlu mengubah testing library kita agar bisa support baik pada fungsi biasa ataupun async function

```js
// assertion-library.js
...
async function test(title, callback) {
  try {
    await callback();
    console.log(`✅  ${title}`);
  } catch (error) {
    console.error(`❌ ${title}`);
    console.log(error);
  }
}
...
```

Dengan kode di atas, pesan yang kita dapat akan seperti berikute
```js
❌ sum async
Error: -4 is not equal to 10
    at Object.toEqual (/assertion-library.js:35:15)
    at /assertion-library.js:18:18
```

well, test gagal dengan pesan error, sesuai yang diharapkan.