Vilnius Ruby Masters

Testing

Topics

  • RSpec components
  • What [not] to test
  • TDD or not TDD?

Boring. Lets code!


              class User
                attr_reader :first_name, :last_name

                def initialize(first_name:, last_name:)
                  @first_name = first_name
                  @last_name = last_name
                end

                def full_name
                  [first_name, last_name].join(' ')
                end
              end
            

RSpec components

Our first test


              describe User do
                subject(:user) do
                  User.new(first_name: 'John', last_name: 'Doe')
                end

                describe '#full_name' do
                  it 'has correct full name' do
                    expect(user.full_name).to eq('John Doe')
                  end
                end
              end
            

`subject`


              subject(:user) do
                User.new(first_name: 'John', last_name: 'Doe')
              end

              describe '#full_name' do
                subject(:full_name) { user.full_name } # !

                it { is_expected.to eq 'John Doe' }
              end
            

`let`


              subject(:user) do
                User.new(first_name: first_name, last_name: last_name) # !
              end

              let(:first_name) { 'John' } # !
              let(:last_name) { 'Doe' } # !

              describe '#full_name' do
                subject(:full_name) { user.full_name }

                it { is_expected.to eq 'John Doe' }
              end
            

`context`


              # ...
              describe '#full_name' do
                subject(:full_name) { user.full_name }

                context 'when last name is missing' do # !
                  let(:last_name) { nil }
                  it { is_expected.to eq 'John' }
                end

                context 'when first and last name are given' do # !
                  it { is_expected.to eq 'John Doe' }
                end
              end
            

Lets code little more!


              class User
                # ...
                def friends_count
                  FacebookApi.get('friends_count')
                end
              end
            

`before`


              # ...
              describe '#friends_count' do
                before do
                  allow(FacebookApi).to receive(:get).and_return(5)
                end

                it 'returns correct friends number' do
                  expect(user.friends_count).to eq 5
                end
              end
            

`allow` (stub)


              # ...
              describe '#friends_count' do
                before do
                  allow(FacebookApi).to receive(:get).and_return(5) # !
                end

                it 'makes facebook request' do
                  user.friends_count
                  expect(FacebookApi).to have_received(:get)
                end
              end
            

`expect` (mock)


              # ...
              describe '#friends_count' do
                it 'makes facebook request' do
                  expect(FacebookApi).to receive(:get).and_return(5) # !
                  user.friends_count
                end
              end
            

What [not] to test?

Lets code a little bit


              class User < ActiveRecord::Base
                validates :name, length: { minimum: 5 }
                validates :email, format: { with: /\w@\w/ }
                # ...
              end

              RSpec.describe User do
                subject(:user) { user.new(email: email, name: name) }
                let(:email) { 'john@example.com' }
                let(:name) { 'john' }
              end
            

Rule #1


              it 'has valid length' do
                expect(User).to validate_length_of(:name).is_at_least(5)
              end
            

Rule #1

Trust yourself. Do not test existence of code

Rule #2


              describe '#valid?' do
                subject(:is_valid) { user.valid? }

                context 'when name is short' do
                  let(:name) { 'j' }
                  it { is_expected.to be false} # better. still not good. Why?
                end

                context 'when email is incorrect' do
                  let(:email) { 'e@@m.zz' }
                  it { is_expected.to be false } # good. Why?
                end
              end
            

Rule #2

Trust your gems test coverage. Do not test logic of your gems

Rule #3


              class User
                MAX_FRIENDS_COUNT = 100
                def non_friends_count
                  MAX_FRIENDS_COUNT - friends_count
                end
              end

              describe '#non_friends_count' do
                before { allow(user).to receive(:friends_count) { 10 } }
                it 'returns correct number' do
                  expect(user.non_friends_count).to eq 90 # good
                  expect(user).to have_received(:friends_count) # bad
                end
              end
            

Rule #3

Test business logic

Rule #4


              # bad
            

Rule #4

Always test your code

TL;DR;

test public methods written by you

TDD or not TDD?

Benefits

But...

This is surprising as it implies the sequence in which writing test and production code are interleaved is not a prominent feature. The finding counters the common folklore within the agile software development community

Summary

  • Tests will save your time
  • Tests will improve quality of your app
  • Tests give you trust when working with code of others

kthxbai