How would I rspec/test an updated_at field without using sleep() in ruby? - ruby

How do i write my spec without using the sleep(1.second) method? When I remove the sleep then my tests break because they are returning the same time stamp?
I have the following class method:
def skip
qs = find_or_create_by(user_id: user_id)
qs.set_updated_at
qs.n_skip += 1
qs.save!
end
and following spec:
qs = skip(user.id)
sleep(1.second)
qs2 = skip(user.id)
qs.should_not be_nil
qs2.should_not be_nil
(qs.updated_at < qs2.updated_at).should be_true

I've used the Timecop gem in the past for doing time based testing.
require 'timecop'
require 'test/unit'
class MyTestCase < Test::Unit::TestCase
def test_mortgage_due_in_30_days
john = User.find(1)
john.sign_mortgage!
assert !john.mortgage_payment_due?
Timecop.travel(Time.now + 30.days) do
assert john.mortgage_payment_due?
end
end
end
So your example may look like:
qs = skip(user.id)
Timecop.travel(Time.now + 1.minute) do
qs2 = skip(user.id)
end
qs.should_not be_nil
qs2.should_not be_nil
(qs.updated_at < qs2.updated_at).should be_true

This also works well for rspec tests. In your Gemfile:
require 'timecop', group: :test
Then, for example, you can use rspec to test a named scope that gets model called queries in descending updated_at order:
require 'timecop'
require 'spec_helper'
describe Query do
# test the named scopes for ordering and searching
describe 'when a query is searched or sorted' do
before :each do
#query1 = create(:query)
Timecop.travel(Time.now + 1.minute) do
#query2 = create(:query)
end
Timecop.travel(Time.now + 2.minute) do
#query3 = create(:query)
end
end
it 'should be listed in descending updated_at order' do
#queries = Query.order_by_latest
#queries.first.should == #query3
#queries.last.should == #query1
end
end
end

Related

RSpec why is before(:each) never executed?

I have this simple code
require 'json'
module Html
class JsonHelper
attr_accessor :path
def initialize(path)
#path = path
end
def add(data)
old = JSON.parse(File.read(path))
merged = old.merge(data)
File.write(path, merged.to_json)
end
end
end
and this spec (reduced as much as I could while still working)
require 'html/helpers/json_helper'
describe Html::JsonHelper do
let(:path) { "/test/data.json" }
subject { described_class.new(path) }
describe "#add(data)" do
before(:each) do
allow(File).to receive(:write).with(path, anything) do |path, data|
#saved_string = data
#saved_json = JSON.parse(data)
end
subject.add(new_data)
end
let(:new_data) { { oldestIndex: 100 } }
let(:old_data) { {"test" => 'testing', "old" => 50} }
def stub_old_json
allow(File).to receive(:read).with(path).and_return(#data_before.to_json)
end
context "when given data is not present" do
before(:each) do
puts "HERE"
binding.pry
#data_before = old_data
stub_old_json
end
it "adds data" do
expect(#saved_json).to include("oldestIndex" => 100)
end
it "doesn't change old data" do
expect(#saved_json).to include(old_data)
end
end
end
end
HERE never gets printed and binding.pry doesn't stop execution and tests fail with message No such file or directory # rb_sysopen - /test/data.json
This all means that before(:each) never gets executed.
Why?
How to fix it?
It does not print desired message because it fails at the first before block. Rspec doc about execution order
It fails because you provided an absolute path, so it is checking /test/data.json
Either use relative path to the test ie. ../data.json (just guessing),
or full path.
In case of rails:
Rails.root.join('path_to_folder_with_data_json', 'data.json')

Writing a test for a case statement in Ruby

I'm trying to write a test for a case statement using minitest. Would I need to write separate tests for each "when"? I included my code below. Right now it just puts statements, but eventually it's going to redirect users to different methods. Thanks!
require 'pry'
require_relative 'messages'
class Game
attr_reader :user_answer
def initialize(user_answer = gets.chomp.downcase)
#user_answer = user_answer
end
def input
case user_answer
when "i"
puts "information"
when "q"
puts "quitter"
when "p"
puts "player play"
end
end
end
This answer will help you. Nonetheless I'll post one way of applying it to your situation. As suggested by #phortx when initializing a game, override the default user-input with the relevant string. Then by using assert_output we can do something like:
#test_game.rb
require './game.rb' #name and path of your game script
require 'minitest/autorun' #needed to run tests
class GameTest < MiniTest::Test
def setup
#game_i = Game.new("i") #overrides default user-input
#game_q = Game.new("q")
#game_p = Game.new("p")
end
def test_case_i
assert_output(/information\n/) {#game_i.input}
end
def test_case_q
assert_output(/quitter\n/) {#game_q.input}
end
def test_case_p
assert_output(/player play\n/) {#game_p.input}
end
end
Running the tests...
$ ruby test_game.rb
#Run options: --seed 55321
## Running:
#...
#Finished in 0.002367s, 1267.6099 runs/s, 2535.2197 assertions/s.
#3 runs, 6 assertions, 0 failures, 0 errors, 0 skips
You have to test each case branch. Via RSpec it would work that way:
describe Game do
subject { Game }
describe '#input' do
expect_any_instance_of(Game).to receive(:puts).with('information')
Game.new('i').input
expect_any_instance_of(Game).to receive(:puts).with('quitter')
Game.new('q').input
expect_any_instance_of(Game).to receive(:puts).with('player play')
Game.new('p').input
end
end
However due the fact that puts is ugly to test, you should refactor your code to something like that:
require 'pry'
require_relative 'messages'
class Game
attr_reader :user_answer
def initialize(user_answer = gets.chomp.downcase)
#user_answer = user_answer
end
def input
case user_answer
when "i"
"information"
when "q"
"quitter"
when "p"
"player play"
end
end
def print_input
puts input
end
end
Then you can test with RSpec via:
describe Game do
subject { Game }
describe '#print_input' do
expect_any_instance_of(Game).to receive(:puts).with('quitter')
Game.new('q').print_input
end
describe '#input' do
expect(Game.new('i').input).to eq('information')
expect(Game.new('q').input).to eq('quitter')
expect(Game.new('i').input).to eq('player play')
expect(Game.new('x').input).to eq(nil)
end
end

Unable to use any_instance on Twitter gem's user_timline

I am still quite fresh to Ruby, and especially testing in Ruby. Hopefully the code is not a trainwreck :) I am having issues using any_instance with the Twitter gem, while it works fine on my own classes.
This is (what I believe) the relevant code
require 'twitter'
require 'minitest/unit'
require 'mocha/mini_test'
omitting for brevity....
args = { id: 573536452149182464, id_str: 73536452149182464, text: 'This is an initial tweet from the user'}
initial_tweet = ::Twitter::Tweet.new(args)
::Twitter::REST::Timelines.any_instance.stubs(:user_timeline).returns(initial_tweet)
The code produces the following error:
Minitest::UnexpectedError: NoMethodError: undefined method `any_instance|' for Twitter::REST::Timelines:Module
Are principles to stubbing gems different, am I approaching it wrong?
EDIT: I have added the entire code for the two classes below.
twitter.rb
require 'rubygems'
require 'cinch'
require 'cinch/commands'
require 'twitter'
require 'shorturl'
module Gigabot
module Commands
class Twitter
include Cinch::Plugin
include Cinch::Commands
def initialize(bot)
super(bot)
#client = create_client
#follow = config[:follow]
#channels = bot.config.channels
#latest_tweets = Hash.new
set_initial_tweets
end
timer 60, method: :twitter_update
def twitter_update
#follow.each do |user|
new_tweet = #client.user_timeline(user, options = {exclude_replies: true}).first
if #latest_tweets[user] != new_tweet
short_url = ShortURL.shorten("https://twitter.com/#{user}/status/#{new_tweet.id}")
reply = Format(:bold, "<#{user}> ") + "#{new_tweet.full_text} [#{short_url}]"
reply = reply.gsub(/\n/,' ')
#channels.each {|channel| Channel(channel).send(reply)}
#latest_tweets[user] = new_tweet
end
end
end
private
def create_client
::Twitter::REST::Client.new do |c|
c.consumer_key = config[:consumer_key]
c.consumer_secret = config[:consumer_secret]
c.access_token = config[:access_token]
c.access_token_secret = config[:access_token_secret]
end
end
def set_initial_tweets
#follow.each do |user|
#latest_tweets[user] = #client.user_timeline(user, options = {exclude_replies: true}).first
end
end
end
end
end
twitter_test.rb
require 'twitter'
require 'minitest/unit'
require 'mocha/mini_test'
require File.dirname(__FILE__) + '/../../../helper'
require File.dirname(__FILE__) + '/../../../../lib/gigabot/commands/twitter'
module Gigabot
module Commands
class TwitterTest < TestCase
def setup
bot = Cinch::Bot.new
bot.loggers.level = :fatal
bot.config.plugins.options[Twitter] = {
consumer_key: 'test_key',
consumer_secret: 'test_key_secret',
access_token: 'test_access_token',
access_token_secret: 'test_access_token_secret',
follow: %w(follow1 follow2)
}
args = { id: 573536452149182464, id_str: 73536452149182464, text: 'This is an initial tweet from the user'}
initial_tweet = ::Twitter::Tweet.new(args)
::Twitter::REST::Timelines.any_instance.stubs(:user_timeline).returns(initial_tweet)
#plugin = Twitter.new(bot)
end
def test_create_twitter_client_on_initialize
refute_nil(#plugin.instance_variable_get(:#client))
end
end
end
end

Ruby: const_set outside block?

I want to mock a class with Ruby.
How do I write a method that will take care of the boilerplate code?
The following code:
module Mailgun
end
module Acani
def self.mock_mailgun(mock)
temp = Mailgun
const_set(:Mailgun, mock)
p Mailgun
yield
ensure
const_set(:Mailgun, temp)
end
end
Acani.mock_mailgun('mock') { p Mailgun }
prints:
"mock"
Mailgun
What's going on here? Why is Mailgun its original value inside the block? Does this have to do with Ruby bindings?
Ruby version: 2.1.1p76
Try putting Object. before each const_set.
The code in the question is simplified. Here is the pertinent code:
test/test_helper.rb
require 'minitest/autorun'
module Acani
def self.const_mock(const, mock)
temp = const_get(const)
const_set_silent(const, mock)
yield
ensure
const_set_silent(const, temp)
end
private
def self.const_set_silent(const, value)
temp = $VERBOSE
$VERBOSE = nil
Object.const_set(const, value)
ensure
$VERBOSE = temp
end
end
test/web_test.rb
require 'test_helper'
require 'rack/test'
require_relative '../web'
class AppTest < MiniTest::Test
include Rack::Test::Methods
def app
Sinatra::Application
end
def test_password_reset
post '/users', {email: 'user1#gmail.com', password: 'password1'}
mailgun_mock = MiniTest::Mock.new
mailgun_mock.expect(:send, 200, [Hash])
Acani.const_mock(:Mailgun, mailgun_mock) do
post '/password_resets', {email: 'user1#gmail.com'}
end
mailgun_mock.verify
assert_equal 201, last_response.status
end
end

Thor::Group do not continue if a condition is not met

I'm converting a generator over from RubiGen and would like to make it so the group of tasks in Thor::Group does not complete if a condition isn't met.
The RubiGen generator looked something like this:
def initialize(runtime_args, runtime_options = {})
super
usage if args.size != 2
#name = args.shift
#site_name=args.shift
check_if_site_exists
extract_options
end
def check_if_site_exists
unless File.directory?(File.join(destination_root,'lib','sites',site_name.underscore))
$stderr.puts "******No such site #{site_name} exists.******"
usage
end
end
So it'd show a usage banner and exit out if the site hadn't been generated yet.
What is the best way to recreate this using thor?
This is my task.
class Page < Thor::Group
include Thor::Actions
source_root File.expand_path('../templates', __FILE__)
argument :name
argument :site_name
argument :subtype, :optional => true
def create_page
check_if_site_exists
page_path = File.join('lib', 'sites', "#{site_name}")
template('page.tt', "#{page_path}/pages/#{name.underscore}_page.rb")
end
def create_spec
base_spec_path = File.join('spec', 'isolation', "#{site_name}")
if subtype.nil?
spec_path = base_spec_path
else
spec_path = File.join("#{base_spec_path}", 'isolation')
end
template('functional_page_spec.tt', "#{spec_path}/#{name.underscore}_page_spec.rb")
end
protected
def check_if_site_exists # :nodoc:
$stderr.puts "#{site_name} does not exist." unless File.directory?(File.join(destination_root,'lib','sites', site_name.underscore))
end
end
after looking through the generators for the spree gem i added a method first that checks for the site and then exits with code 1 if the site is not found after spitting out an error message to the console. The code looks something like this:
def check_if_site_exists
unless File.directory?(path/to/site)
say "site does not exist."
exit 1
end
end

Resources