Hi I have a Ruby class with some constant variables using Date:
START_DATE = Date.current.at_beginning_of_month.in_time_zone + 2.days
LAST_DATE = Date.current.at_beginning_of_month.in_time_zone + 10.days
and I have some methods which uses this date inside like below:
Date.current.in_time_zone.between?(START_DATE, LAST_DATE)
in my rspec file I'm using Timecop.freeze and it's breaking my tests.
Is there a workaround to use the same variable for most of my methods? or am I using this incorrectly?
I would appreciate any help!
I actually got this answer from the Ruby slack community I got a suggestion to make it a method.
so something like:
def start_date
Date.current.at_beginning_of_month.in_time_zone + 2.days
end
I also just learned what #spickermann meant with why I shouldn't use constant variable because it will stay constant from the start of the server it will have the initial value. and technically, it's not a constant. :sweatsmile:
Whether or not you use Timecop for other interactions in your tests, you may also want to consider stubbing the constants themselves. Once you've tested the logic involved with setting the constants, consider using stub_const to ensure that the constants are set to the values you want in your test suite. For example, you might include a block in your test suite that looks something like this:
before :each do
stub_const("MyClass::START_DATE", <start_time>)
stub_const("MyClass::END_DATE", <end_time>)
end
Updated:
Comment below says this doesn't work, which is odd... definitely works for me. Just tested this like this:
class User
MY_CONST = "foo"
def my_method
MY_CONST
end
end
and then in rspec:
describe User do
it "works unstubbed" do
expect(User.new.my_const).to eq("foo")
end
it "works stubbed" do
stub_const("User::MY_CONST", "bar")
expect(User.new.my_const).to eq("bar")
end
end
Related
I have a model that has a constant that looks like:
SOURCE_QUOTA = {
'free' => (ENV['FREE_SOURCE_QUOTA'] || '5').to_i,
'premium' => (ENV['PREMIUM_SOURCE_QUOTA'] || '100').to_i
}.freeze
RuboCop suggests "Style/MutableConstant" and that the freeze should be there.
However, during testing, I want to use slightly different values. Now, I could actually modify the ENV of the bash session where I launch rspec. Sure. But modifying it in the code makes life easier. For example:
describe "a thing" do
it "does stuff" do
ENV['FREE_SOURCE_QUOTA'] = '2'
DataSource::SOURCE_QUOTA["free"] = ENV['FREE_SOURCE_QUOTA'].to_i
# test code
The above works when the constant isn't frozen. I assume that's because, when frozen, the Model is loaded during the loading of the Rails environment and then this value cannot be muted (it's frozen!).
What would be the best thing to do here?
stick with frozen constant and be sure to set desired env before the rspec run?
don't freeze the constant and keep the above test
do something else that involves unfreezing/refreezing/reloading model?
Thanks!
I would think anout g a class method instead of a constant. Something like this:
# in your model
def self.source_quota
{
'free' => (ENV['FREE_SOURCE_QUOTA'] || '5').to_i,
'premium' => (ENV['PREMIUM_SOURCE_QUOTA'] || '100').to_i
}
end
and then mock that method in your tests like this:
allow(DataSource).to receive(source_quota)and_return('free' => 2)
One way is to use RSpec's stub_const feature. This allows you to stub the constant value to whatever you want in your example.
Your test code might look something like
describe 'a thing' do
before { stub_const('DataSource::SOURCE_QUOTA', {'free' => 2}) }
it 'does stuff' do
expect(DataSource::SOURCE_QUOTA["free"]).to eq 2
# test code
I have the following module in a Rails api:
module SpeedCalculator
def self.adjustment
Random.rand(0..0.3)
end
def self.calculate_adjustment(track, car_speed)
case track.surface_type
when "asphalt"
car_speed - (car_speed * self.adjustment).ceil
when "gravel"
car_speed - (car_speed * self.adjustment).ceil
when "snow"
car_speed - (car_speed * self.adjustment).ceil
else
car_speed
end
end
end
I can successfully test that the adjustment method works like this:
require 'rails_helper'
RSpec.describe SpeedCalculator do
include SpeedCalculator
it "uses an adjustment value between 0 and 0.3" do
expect(SpeedCalculator.adjustment).to be >= 0
expect(SpeedCalculator.adjustment).to be <= 0.3
end
end
It is possible to make an API request like this:
localhost:3000/api/v1/cars/porsche-911?track=monaco
where you are asking the system to calculate the speed for the given car on the given track.
So I need to write a request spec that for a given car and track, the correct value is returned. But how can I do that when the calculate_adjustment always applies a random number?
I believe that I need to create a mock/stub for self.adjustment, so the test would be something like this:
it "calculates max_speed_on_track when a valid track name is provided" do
Car.create!(name:'Subaru Impreza', max_speed:'120', speed_unit:'km/h')
Track.create(name:'Monaco')
# here create a dummy version of adjustment
# that always returns a fixed value, rather than a random value
# and somehow use that dummy for the request?
# Since x below needs to be a known value for the test to work.
get api_v1_car_path(id: 'porsche-911', track: 'monaco')
expect(response).to have_http_status(200)
expect(response.body).to include_json(car: {max_speed_on_track: x})
end
how can I do that when the calculate_adjustment always applies a random number?
I believe that I need to create a mock/stub for self.adjustment
Exactly! The last thing you want in tests is random behaviour (and random failures). You want reproducible results. So yeah, mock your RNG.
The simplest thing to do would be this:
expect(Random).to receive(:rand).with(0..0.3).and_return(0.1234)
# or better
expect(SpeedCalculator).to receive(:adjustment).and_return(0.1234)
# then proceed with your test
get api_v1_car_path(id: 'porsche-911', track: 'monaco')
...
A further improvement here is to not use controller specs to test business logic. Encapsulate your business logic in an object and test that object (where you can use Dependency Injection technique to full extent). And your controller would simply become something like this:
class CarsController
def show
calculator = MaxSpeedCalculator.new(params[:id], params[:track])
render json: calculator.call
end
end
Very often when I have a missed expectation in a unit test using mocha, it spits out dozens or hundreds of "satisfied expectations" that I really don't care about. There's so much of it that I have to redirect testing output to a temp file, which is really annoying.
I am using Rails 2.3 and Mocha 0.10.0.
To clarify, I have the same issue as in
Mocha Mock Carries To Another Test
, and the solution there has not worked for me. However, even if I can get that resolved, I would like to suppress "satisfied expectations".
Thanks.
You could monkey patch mocha to achieve this. There's a method on Mocha::Mockery that returns the satisfied expectations which you could patch to return an empty array:
module Mocha
class Mockery
def satisfied_expectations
[]
end
end
end
If you put this in test_helper.rb it'll get picked up.
Alternatively for a bit more flexibility you could opt to only hide them when an environment variable is set:
module Mocha
class Mockery
def satisfied_expectations_with_optional
if ENV['MOCHA_HIDE_SATISFIED']
[]
else
satisfied_expectations_without_optional
end
end
alias_method_chain :satisfied_expectations, :optional
end
end
Then run your tests like this:
> MOCHA_HIDE_SATISFIED=1 rake test
module ApplicationHelper
def title(page_title, show_title = true)
content_for(:title) do
page_title.to_s
end
#show_title = show_title
end
end
Anyone knows how can I test this helper using test unit?
For any helper testing in rails, you always start in tests/unit/helpers.
Since this is a ApplicationHelper, use the file called application_helper_test.rb
In that file you can have something like
test "displays page title" do
assert_equal "April 2010", title("April 2010", false)
end
You can test whatever is returned in a helper by just calling the method as usual, and asserting something is sent back.
Not knowing what you are doing, personally, there is too much going on in this method, but that could just be me.
I'd break these two out, so that your helper is just returning a page_title and another one is returning a "show_title" whatever that is. or is that like your switch to say " I should show this title on a page"?
I would like to implement the method User.calculate_hashed_password. I'm trying to use the Shoulda testing library which works with Rails's built-in testing tools, so an answer related to Test::Unit would be just as good as one related to Shoulda (I think).
I'm trying to figure out what I need to test and how I should test it. My initial idea is to do something like...
class UserTest < ActiveSupport::TestCase
should 'Return a hashed password'
assert_not_nil User.calculate_hashed_password
end
end
Is this the right way to do it?
You don't need to test that the method exists, just that the method behaves correctly. Say something like this:
class UserTest < ActiveSupport::TestCase
setup do
#user = User.new
end
should 'Calculate the hashed password correctly'
#user.password = "password"
#user.hashed_password = "xxxxx" # Manually calculate it
end
end
(I don't use shoulda, so excuse any glaring syntax errors.)
That test will fail if the method doesn't exist.
I agree with Otto; but as dylanfm noted, I use #respond_to to test for associations in RSpec.
it "should know about associated Projects" do
#user.should respond_to(:projects)
end
Maybe use respond_to?
You should check out Object#respond_to? and Object#try in newer versions of Rails. If you're new to testing in general, definitely read through this excellent guide on testing in Rails.