React dan TDD dalam 10 Menit — Part 2

Di artikel sebelumnya kita telah membuat komponen TodoList dan Todo. Kali ini kita akan lanjut ke komponen selanjut-nya yaitu TodoForm.

Komponen 3: TodoForm

TodoForm berguna untuk menambahkan todo. Ketika suatu todo ditambahkan, kita mengharapkan agar todo tersebut bisa tersimpan di state komponen parent. Caranya kita akan pass sebuah fungsi ke TodoForm dari parent komponen. Ketika sebuah todo disubmit, maka fungsi ini akan dipanggil.

Spesifikasi ability dari komponen ini:

  • Tidak bisa submit todo, ketika todo kurang 5 karakter
  • Bisa submit todo baru, ketika todo lebih dari 5 karakter

jest.fn() berguna untuk membuat fungsi spy, yang berguna untuk mengetahui apakah fungsi yang kita definisikan terpanggil atau tidak, jika terpanggil, berapa kali, dipanggil dengan argumen apa, dan seterusnya.

onSubmitSpy berguna sebagai fungsi yang akan kita pass dari parent komponen TodoForm. preventDefaultSpy sebagai stub, ketika elemen form terjadi event submit. Ketika submit, kita ingin agar preventDefault() terpanggil agar halaman tidak te-refresh. Karena setelah form di submit kita ingin input yang ada di form tersebut direset, kita harus mendefinisikan resetSpy.

Assertions

Ketika todo di-submit, kita ingin preventDefault selalu dipanggil.

expect(preventDefaultSpy).toHaveBeenCalled();

Ketika kurang dari 5 karakter, kita tidak ingin onSubmit dan reset tidak dipanggil

expect(onSubmitSpy).not.toHaveBeenCalled();  expect(resetSpy).not.toHaveBeenCalled();

Sebaliknya ketika todo 5 karakter atau lebih kita ingin agar keduanya dipanggil

expect(onSubmitSpy).toHaveBeenCalledWith('somes');
expect(resetSpy).toHaveBeenCalled();

Pertama kita cari elemen form dengan class .todo-form. Ketika ditemukan, kita simulasi event submit dengan method simulate(), argument pertama nama event, argument kedua adalah object event. Di argument kedua ini, kita assign fungsi-fungsi spy yang sudah kita buat.

subject.find('.todo-form').simulate('submit', {
preventDefault: preventDefaultSpy,
target: {
todo: { // attribute name <input name="todo" />
value: 'somes' // simulasi input value 5 karakter
},
reset: resetSpy,
}
});

Implementasi

import React, { Component } from 'react';
export default class TodoForm extends Component {
submitForm = (event) => {
// ketika test dijalankan, method ini merupakan spy preventDefaultSpy
event.preventDefault();
const { target } = event;
const todoText = target.todo.value;
    // ketika todo kurang dari 5 karakter, segera berhenti
if (todoText.length < 5) {
return;
}
    this.props.onSubmit(todoText);
target.reset();
};
render() {
return (
<form className="todo-form" onSubmit={this.submitForm}>
<h3 className="todo-form__header">Another todo?</h3>
<input name="todo" className="todo-form__input-text" type="text" />
<button className="todo-form__button-submit" type="submit">
Submit
</button>
<br />
<span><small>Note: it should be 5 or more characters</small></span>
</form>
);
}
}

Ketika test dijalankan

Bonus — Integration Test —

Yang dimaksud integration test di sini ialah interaksi antar komponen harus sesuai dengan ekspektasi. Jika kita menambahkan todo baru melalu komponen TodoForm, maka state pada komponen App akan ikut ter-update.

Pertama, kita ingin ada fungsi untuk menambah todo. Fungsi ini akan di-pass ke komponen TodoForm.

// src/App.test.js
it('should have addTodo method', () => {
const subject = shallow(<App />);
  const todoCount = subject.state('todos').length;
  subject.instance()
.addTodo('Something else');
  expect(subject.state('todos').length).toBe(todoCount + 1);
});

Implementasi

import React, { Component } from 'react';
import TodoList from './components/TodoList';
import './App.css';
class App extends Component {
state = {
todos: [
{ id: 1, text: 'Hello', done: false },
{ id: 2, text: 'There', done: true },
{ id: 3, text: "It's cool, isn't it?", done: false },
],
};
  addTodo = (todoText) => {
addTodo = (todoText) => {
const { todos } = this.state;
const todoCount = todos.length;
const nextId = todos[todoCount - 1].id + 1;
const nextTodo = { id: nextId, text: todoText, done: false };
this.setState({
todos: todos.concat(nextTodo),
});
};
  render() {
return (
<div className="App">
<TodoList entries={this.state.todos} />
</div>
);
}
}
export default App;

Output

Test selanjutnya, merupakan integration test menggunakan mount(). Ketika kita melakukan simulasi submit todo dari komponen TodoForm, diharapkan state komponen App ikut ter-update.

it('should able to add new todo', () => {
const subject = mount(<App />);
  const todoCount = subject.state('todos').length;
  // Fail scenario
subject.find('.todo-form').simulate('submit', {
target: {
todo: { value: 'New'},
reset: () => {},
},
});
  // jumlah todo harus tetap
expect(subject.state('todos').length).toBe(todoCount);
  // Success scenario
subject.find('.todo-form').simulate('submit', {
target: {
todo: { value: 'New todo'},
reset: () => {},
},
});
  // jumlah todo harus bertambah
expect(subject.state('todos').length).toBe(todoCount + 1)
expect(subject.find('Todo').last().text()).toBe('New todo');
});

Implementasi

import React, { Component } from 'react';
import TodoList from './components/TodoList';
import TodoForm from './components/TodoForm';
import './App.css';
class App extends Component {
state = {
todos: [
{ id: 1, text: 'Hello', done: false },
{ id: 2, text: 'There', done: true },
{ id: 3, text: "It's cool, isn't it?", done: false },
],
};
  addTodo = (todoText) => {
addTodo = (todoText) => {
const { todos } = this.state;
const todoCount = todos.length;
const nextId = todos[todoCount - 1].id + 1;
    const nextTodo = { id: nextId, text: todoText, done: false };
    this.setState({
todos: todos.concat(nextTodo),
});
};
  render() {
return (
<div className="App">
<TodoList entries={this.state.todos} />
<TodoForm onSubmit={this.addTodo} />
</div>
);
}
}
export default App;

Output

Coba jalankan aplikasinya

npm start

Penampakannya akan seperti ini

Congrats!!! Kita berhasil membuat todo app dengan TDD 😎


What Next

Aplikasi yang kita buat sangat simpel. Jika ingin, teman-teman bisa menambahkan fitur lainnya seperti:

  • Error message pada form, ketika input kurang dari 5 karater
  • Membuat button done dan delete pada komponen done button

Penutup

Teman-teman bisa mengunjungi website Jest dan Enzyme untuk mengetahui lebih lanjut tentang api dari keduanya. Jangan takut untuk mencoba hal-hal baru.

Jika ada kesempatan, selanjutnya kita akan membahas tentang end-to-end testing menggunakan aplikasi todo ini. Thanks!

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.