I met something tricky or interesting in writing test scripts and I want to share them.
There are no Chinese input methods in this PC. And I'm too lazy to install one Let me write in English.
Hope they are helpful.
Operations on float numbers sometimes return things unexpectedly. For example:
it "sums two float numbers properly" do
expect(0.7+0.1).to eq(0.8)
end
Looks OK? But actually this is what you'll get:
expected: 0.8
got: 0.7999999999999999
(compared using ==)
The reason is that Ruby follows IEEE, which may cause some imprecisions in float values. And I guess it varies with different systems or even Ruby versions. It's a very common issue within many programming languages.
// Javascript
console.log(0.1+0.7)
// => 0.7999999999999999
# Python 2.7.12
0.1+0.7
=> 0.7999999999999999
But not all the languages.
-- Lua 5.2.4
print(0.1+0.7)
=> 0.8
And fortunately, SQL works correctly.
-- SQL in (PostgreSQL) 9.5.4
select (0.1+0.7) as sum;
sum
-----
0.8
(1 row)
The solution is simple, just use the be_within
matcher.
it "sums two float numbers properly" do
expect(0.7+0.1).to be_within(0.1).of(0.8)
end
This is my favorite syntax in RSpec. It shortens our codes when we need to assert value changes.
For example in a controller spec:
it 'schedules a new attack task' do
expect{post :create, attack_params}.to change{AttackTask.count}.by(1)
end
I pick a piece of codes from my game. This is not a very good example, because the conception of ControllerTest in Rails 5 has been changed.
Actually, you can use a single from
, to
or by
, or a combination of them if it is meaningful, or even none of them. I really love this.
expect { i.increase }.to change {i}.by(1)
expect { radix += 2 }.to change {radix}.from(0).to(2)
expect { someone.execute! }.to change { someone.status }.to('dead')
expect { flag.reverse }.to change {flag}
And lastly, a tricky example with what I noticed in the first section:
expect { i += 1.05 }.to change { i }.by( a_value_within(0.01).of(1.05) )
In some situations, you have to work with time or durations. And it is complicated for us to test them as time will never stop.
You don't have magic in the real world to freeze time, but in the programming world, yes, you do, with the power of timecop.
A very simple example:
it 'should assign ended_at' do
stubbed_time = Time.new(2016, 12, 25)
Timecop.freeze(stubbed_time)
expect{task.finish!}.to change{task.ended_at}.to(stubbed_time)
end
Ask yourself whether you really have to use after_commit
, then whether you need to test it.
If both of the answers are 'yes', then you start to write you specs and you may face this issue that after_commit
callbacks are NOT getting called in transactional tests (at least in Rails < 5).
What you need is to try this gem test_after_commit.
Well, the good news is that it will be fixed in Rails 5 https://github.com/rails/rails/pull/18458.
ActiveJob has become a component of Rails since 4.2. And you really don't need to test ActiveJob functionality. But I just want to cover everything as if I am a Vigro programmer. I can give an simple example of ActiveJob specs.
One of my jobs:
class NoticeMailForNewUserJob < ActiveJob::Base
queue_as :default
def perform(user)
AdminMailer.notify_new_player_registration(user).deliver_now
end
end
And configs:
module MyProject
class Application < Rails::Application
#....something
# ActiveJob
config.active_job.queue_adapter = :sidekiq
config.active_job.queue_name_prefix = Rails.env
end
end
And here are the specs:
require 'rails_helper'
RSpec.describe NoticeMailForNewUserJob, type: :job do
include ActiveJob::TestHelper
let(:player) { create :user }
subject(:job) { described_class.perform_later(player) }
after do
clear_enqueued_jobs
clear_performed_jobs
end
it 'queues the job' do
expect{ job }.to change { ActiveJob::Base.queue_adapter.enqueued_jobs.size }.by(1)
end
it 'is in default queue' do
expect(described_class.new.queue_name).to eq('test_default')
end
it 'performs' do
expect(AdminMailer).to receive_message_chain(:notify_new_player_registration, :deliver_now)
perform_enqueued_jobs { job }
end
end
Don't use respond_to
to test methods generated by #method_missing
. It will not work.
Run $ rake stats
, you can see the Code to Test Ratio
.