Sejak memasang "dark" theme, saya cenderung menjadi malas menulis. Untuk sementara, dark theme saya disable dulu yaa. Terima kasih (^_^) (bandithijo, 2024/09/15) ●

بسم الله الرحمن الرحيم

Prerequisite

Ruby 2.6.3 Rails 6.0.1 PostgreSQL 11.5 RSpec 4.0.0.beta3

Prakata

Sekitar seminggu yang lalu, di saat badan sedang meriang dan meler hebat namun kadang tersumbat, saya mencoba untuk mempelajari salah satu test atau pengujian untuk membantu kita menguji web aplikasi yang kita bangun.

RSpec, adalah salah satu test tersebut.

Selain RSpec, Ruby on Rails sendiri, sudah memiliki built-in test.

Keren bukan!

Test tersebut bernama MiniTest.

MiniTest ini sudah menjadi test bawaan dari Rails sejak versi 5.1.

Ini adalah bukti yang sangat bagus sekali, untuk menjelaskan bahwa dalam membuat sebuah web aplikasi, kita sangat perlu untuk melakukan testing.

Kalau saya tidak ngawur, konsep ngoding sambil melakukan testing itu dikenal dengan Test Driven Development (TDD).

Kenapa RSpec?

Kalau Ruby on Rails sudah membawa MiniTest secara default, lantas mengapa memilih menggunakan RSpec?

Ini pendapat pribadi saya.

Menurut saya, dalam menulis spesifikasi-spesifikasi test, RSpec memiliki sintaks yang mudah untuk dibaca. Karena mudah dibaca tentunya akan mudah untuk dipahami. Bahkan untuk orang non-technical akan sangat mudah memahami spesifikasi test yang ditulis menggunakan RSpec.

Coba perhatikan contoh dibawah ini.

Berikut ini adalah beberapa spesifikasi list pada model Author.

FILEspec/models/author_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
RSpec.describe Author, type: :model do
  context 'Validation Presence Tests' do
    it 'Ensures full name presence'
    it 'Ensures email presence'
    it 'Ensures password presence'
    it 'Should save successfully'
  end

  context 'Validation Length Tests' do
    it 'Ensures full name character length, more than 5'
    it 'Ensures full name character length, less than 30'
    it 'Ensures email character length, less than 50'
    it 'Ensures password character length, same or more than 8'
  end

  context 'Email Format Tests' do
    it 'Ensures email format not valid'
    it 'Ensures email format valid'
  end

  context 'Email Uniqueness Tests' do
    it 'Ensures email has uniqueness'
  end
end

Setiap list tersebut, nantinya akan saya breakdown sesuai dengan konteksnya dan judulnya.

Gimana?

Cukup dapat dimengerti kan, maksud dari list tersebut.

Nah, sekarang saya lanjutkan untuk proses memasang RSpec pada project Rails kita.

Instalasi

Pada project baru, saya menjalankan perintah ini.

$ rails new blog_rspec_test -d postgresql -T

Penambahan option -T, adalah untuk men-disable built-in test pada project yang baru kita buat.

Tujuannya tentu saja, karena saya akan menggunakan RSpec untuk melakukan testing, bukan menggunakan MiniTest.

Pada project yang sudah ada, langsung saja mengikuti langkah selanjutnya.

Kemudian, pasang gem rspec-rails pada block :development dan :text di Gemfile project.

FILEGemfile
1
2
3
4
5
6
7
# ...
# ...

group :development, :test do
  # ...
  gem 'rspec-rails', '~> 4.0.0.beta3'
end

Tujuan memasukkan gem ini pada group :development agar lebih mudah. Karen, kalau hanya pada group :test, kita hanya dapat mendapatkan gem ini evinmonment test (RAILS_ENV=test).

Saya menggunakan versi 4.0.0.beta3 karena terdapat test yang sudah deprecated pada versi sebelumnya, yaitu pada controller test. Karena belum begitu memahami lebih jauh tentang RSpec, saya mengikuti saja saran dari teman-teman yang sudah lebih dulumencoba versi beta ini.

Selanjutnya, seperti biasa, setiap setelah menambahkan gem baru pada Gemfile, kita perlu menjalankan perintah,

$ bundle install

Setelah proses instalasi selesai, kita juga perlu meng-generate boilerplate dari konfigurasi yang sudah disediakan oleh rspec-rails.

$ rails generate rspec:install

Hasil generate tersebut, akan membuat beberapa file konfigurasi pada direktori rspec/.

Running via Spring preloader in process 28211
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

Langkah selanjutnya ini bersifat optional, tapi saya memilih untuk melakukannya.

Tambahkan --format documentation, pada file .rspec.

--require spec_helper
--format documentation

Penambahan ini bertujuan untuk mengganti format default output yang ditampilkan oleh RSpec menjadi lebih mudah untuk dibaca.

Kalau tidak ingin menambahkannya sekarang, kita juga dapat menambahkannya dilain waktu.

Nah, sekarang kita dapat lanjut pada tahapan membuat spesifikasi test.

Model Specs

Saya sependapat dengan pernyataan bahwa, “Untuk memahami apa itu test, paling mudah kita mulai dari model test.”

Nah, karena alasan itu saya memulai dari spesifikasi model terlebih dahulu.

Kenapa, karena kita dapat memanfaatkan validation yang terdapat di dalam model. Hihihi.

RSpec juga sudah menyediakan spec file generator. Tinggal kita pergunakan saja. Enak sekali kan.

$ rails generate rspec:model nama_model

Pada, kasus ini, saya memiliki nama model author.

$ rails generate rspec:model author

Maka, rspec akan men-generate satu file spec untuk kita.

      create  spec/models/author_spec.rb

Untuk melihat daftar dari generator apa saja yang disediakan oleh RSpec, dapat menggunakan perintah,

$ rails generate --help | grep rspec

Sebelum saya menjabarkan spesifikasi model test untuk model author, saya akan menunjukkan isi dari model author yang di dalamnya terdapat daftar validation dari model author.

FILEapp/models/author.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Author < ApplicationRecord
  has_many :articles

  # Validations
  validates :full_name, presence: true,
                        length: {minimum: 5, maximum: 30}

  VALID_EMAIL_REGEX = /\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
  validates :email, presence: true,
                    uniqueness: {case_sensitive: false},
                    length: {maximum: 50},
                    format: {with: VALID_EMAIL_REGEX }

  validates :password, presence: true,
                       length: {minimum: 8}
end

Nah, sekarang pasti sudah mengerti kan, kenapa untuk belajar, sangat mudah kita mulai dari model test.

Karena kita akan menguji fungsi dari validation yang sudah kita definisikan pada model author.

Oke sekarang langsung saja, saya akan menjabarkan spesifikasi untuk menguji validation untuk presence: true pada setiap field.

Kita dapat melihat pada validation model author tersebut, setiap field memiliki presence validation.

Saya akan mulai dari filed full_name terlebih dahulu.

FILEspec/models/author_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
require 'rails_helper'

RSpec.describe Author, type: :model do
  context 'Validation Presence Tests' do
    it 'Ensures full name presence' do
      author = Author.new(
        full_name: nil,
        email: Faker::Internet.free_email,
        password: Faker::Team.name.downcase.strip.gsub(' ', '')
      ).save
      expect(author).to eq(false)
    end
  end
end

Pada spesifikasi ini, saya hanya membuat filed full_name bernilai nil. Dengan maksud membuat field tersebut kosong. Dan test dianggap benar, apabila hasil dari spesifikasi tersebut bernilai salah ` eq(false)`.

Mudah kan!

Nah, coba perhatikan, terdapat blok yang bernama context. Ini dapat kita gunakan untuk membuat konteks pada setiap spesifikasi testing yang akan kita lakukan.

Langkah selanjutnya adalah presence validation untuk field email, maka saya masukkan di dalam context yang sama, dan berada di di bawah spesifikasi untuk field full_name.


...
    ...

    it 'Ensures email presence' do
      author = Author.new(
        full_name: Faker::Team.name.titlecase,
        email: nil,
        password: Faker::Team.name.downcase.strip.gsub(' ', '')
      ).save
      expect(author).to eq(false)
    end

    ...
...

Begitu seterusnya untuk presence validation pada field password.

Coba teman-teman buat spesifikasinya untuk presence validation pada field password ini.

Oh ya!

Saya menggunakan bantuan gem faker untuk meng-generate data dummy. Agar lebih mudah, dan tidak perlu menghabiskan waktu untuk memikirkan data secara manual.

Nah, dengan begitu, saya langsung dapat menuliskan spesifikasi untuk menguji semua spesifikasi validation yang ada pada model author.

Kira-kira seperti ini.

FILEspec/models/author_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
require 'rails_helper'

RSpec.describe Author, type: :model do
  context 'Validation Presence Tests' do
    it 'Ensures full name presence' do
      author = Author.new(
        full_name: nil,
        email: Faker::Internet.free_email,
        password: Faker::Team.name.downcase.strip.gsub(' ', '')
      ).save
      expect(author).to eq(false)
    end

    it 'Ensures email presence' do
      author = Author.new(
        full_name: Faker::Team.name.titlecase,
        email: nil,
        password: Faker::Team.name.downcase.strip.gsub(' ', '')
      ).save
      expect(author).to eq(false)
    end

    it 'Ensures password presence' do
      author = Author.new(
        full_name: Faker::Team.name.titlecase,
        email: Faker::Internet.free_email,
        password: nil
      ).save
      expect(author).to eq(false)
    end

    it 'Should save successfully' do
      author = Author.new(
        full_name: Faker::Team.name.titlecase,
        email: Faker::Internet.free_email,
        password: Faker::Team.name.downcase.strip.gsub(' ', '')
      ).save
      expect(author).to eq(true)
    end
  end

  context 'Validation Length Tests' do
    it 'Ensures full name character length, more than 5' do
      author = Author.new(
        full_name: 'ban',
        email: Faker::Internet.free_email,
        password: Faker::Team.name.downcase.strip.gsub(' ', '')
      ).save
      expect(author).to eq(false)
    end

    it 'Ensures full name character length, less than 30' do
      author = Author.new(
        full_name: 'bandithijobandithijobandithijobandithijo',
        email: Faker::Internet.free_email,
        password: Faker::Team.name.downcase.strip.gsub(' ', '')
      ).save
      expect(author).to eq(false)
    end

    it 'Ensures email character length, less than 50' do
      author = Author.new(
        full_name: Faker::Team.name.titlecase,
        email: 'bandithijobandithijobandithijobandithijobandithijo@gmail.com',
        password: Faker::Team.name.downcase.strip.gsub(' ', '')
      ).save
      expect(author).to eq(false)
    end

    it 'Ensures password character length, same or more than 8' do
      author = Author.new(
        full_name: Faker::Team.name.titlecase,
        email: Faker::Internet.free_email,
        password: 'bandit'
      ).save
      expect(author).to eq(false)
    end
  end

  context 'Email Format Tests' do
    it 'Ensures email format not valid' do
      author = Author.new(
        full_name: Faker::Team.name.titlecase,
        email: 'bandithijo@bandithijo',
        password: Faker::Team.name.downcase.strip.gsub(' ', '')
      ).save
      expect(author).to eq(false)
    end

    it 'Ensures email format valid' do
      author = Author.new(
        full_name: Faker::Team.name.titlecase,
        email: Faker::Internet.free_email,
        password: Faker::Team.name.downcase.strip.gsub(' ', '')
      ).save
      expect(author).to eq(true)
    end
  end

  context 'Email Uniqueness Tests' do
    before do
      Author.new(
        full_name: Faker::Team.name.titlecase,
        email: 'bandithijo@gmail.com',
        password: Faker::Team.name.downcase.strip.gsub(' ', '')
      ).save
    end

    it 'Ensures email has uniqueness' do
      author = Author.new(
        full_name: Faker::Team.name.titlecase,
        email: 'bandithijo@gmail.com',
        password: Faker::Team.name.downcase.strip.gsub(' ', '')
      ).save
      expect(author).to eq(false)
    end
  end
end

Nah, gimana?

Mudah dipahami kan?

Nah, untuk menalankan test-nya gunakan perintah ini,

$ bundle exec rspec

Nanti akan keluar output seperti ini.

Author
  Validation Presence Tests
    Ensures full name presence
    Ensures email presence
    Ensures password presence
    Should save successfully
  Validation Length Tests
    Ensures full name character length, more than 5
    Ensures full name character length, less than 30
    Ensures email character length, less than 50
    Ensures password character length, same or more than 8
  Email Format Tests
    Ensures email format not valid
    Ensures email format valid
  Email Uniqueness Tests
    Ensures email has uniqueness

Finished in 2.1 seconds (files took 8.29 seconds to load)
11 examples, 0 failures

Sesuai dengan jumlah spesifikasi yang kita tulis, ada 11 buah. Dan kesemuanya berhasil.

Selanjutnya untuk controller spec.

Controller Spec

Kita gunakan lagi spec file generator yang sudah disediakan oleh RSpec.

$ rails generate rspec:controller authors

Karena kita akan menguji controller, tentu saja kita mengikuti naming convention dari Rails, yang mengharuskan menggunakan penamaan plural pada controller. Berbeda dengan model yang menggunakan penamaan singular.

Kalau berhasil, maka akan dibuatkan file specnya seperti ini.

      create  spec/controllers/authors_controller_spec.rb

Belum banyak yang saya pahami mengenai controller spec ini, jadi langsung saja saya tulisakan sedikit contohnya mengenai pengujian untuk response dan route.

Kira-kira seperti ini.

FILEspec/controllers/authors_controller_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
require 'rails_helper'

RSpec.describe AuthorsController, type: :controller do
  context 'Get #index' do
    it 'Returns a success response' do
      get :index
      expect(response).to be_ok
    end
  end

  context 'Get #show' do
    it 'Returns a success response' do
      author = Author.create!(
        full_name: Faker::Team.name.titlecase,
        email: Faker::Internet.free_email,
        password: Faker::Team.name.downcase.strip.gsub(' ', '')
      )
      get :show, params: { id: author.to_param }
      expect(response).to be_ok
    end
  end

  context 'Proper Routes Tests' do
    it 'Should has proper index route' do
      expect(get: '/authors').to be_routable
    end

    it 'Should has proper show route' do
      expect(get: '/authors/1').to be_routable
    end

    it 'Should has proper new route' do
      expect(get: '/authors/new').to be_routable
    end

    it 'Should has proper edit route' do
      expect(get: '/authors/1/edit').to be_routable
    end

    it 'Should has proper destroy route' do
      expect(delete: '/authors/1').to be_routable
    end
  end
end

Wkwkwkwk.

Pengen ketawa, karena seadanya banget.

Tapi tidak mengapa, saya tetap harus menuliskan catatan mengenai proses belajar ini.

Sebagai jejak belajar yang menunjukkan bahwa saya dulu juga berawal dari belum bisa.

Okeh!

Sepertinya hanya ini saja yang ingin saya tuliskan.

Untuk referensi yang lebih lengkap seputar RSpec pada Rails, dapat dimulai dari membaca dokumentasi pada gem rspec-rails yang saya sertakan pada referensi di bawah.1

Untuk referensi seputar RSpec dapat membaca pada dokumentasi RSpec yang disediakan di Relishapp.3

Mudah-mudahan dapat sedikit banyak bermanfaat buat teman-teman yaa.

Terima kasih.

(^_^)v

Referensi

  1. github.com/rspec/rspec-rails
    Diakses tanggal: 2019/12/05

  2. Everyday Rails Testing with RSpec : A practical approach to test-driven development by Aaron Sumner
    Diakses tanggal: 2019/12/05

  3. relishapp.com/rspec/rspec-rails/docs
    Diakses tanggal: 2019/12/05


Penulis

bandithijo

My journey kicks off from reading textbooks as a former Medical Student to digging bugs as a Software Engineer – a delightful rollercoaster of career twists. Embracing failure with the grace of a Cat avoiding water, I've seamlessly transitioned from Stethoscope to Keyboard. Armed with ability for learning and adapting faster than a Heart Beat, I'm on a mission to turn Code into a Product.

- Rizqi Nur Assyaufi

944e8edeccab170ecee65673676b75514b2f62ed