I need help figuring out how to generate a unique identifier for each example in my rspec tests. What do I change for the below code to work?
describe 'Verify that my server' do
#x = 1
it "does something " + #x.to_s do
2.should==2
end
it "does something else " + #x.to_s do
2.should==2
end
after(:each) do
#x+=1
end
end
Take a look at ffaker for generating random values in tests. It can generate real-looking random data, like e-mail addresses, IP addresses, phone numbers, people's names etc, but it also has basic methods for generating random strings of letters and numbers.
Faker.numerify("###-###-###")
# => 123-456-789
Alternatively you can use stdlib's SecureRandom.
Each example in your Rspec should complete a sentence, a sentence you usually start in a describe block encapsulating related tests.
I took this from one of my own specs:
describe Redis::BigHash do
before :each do
#hash = Redis::BigHash.new
#hash[:foo] = "bar"
#hash[:yin] = "yang"
end
describe "#[]" do
it "should read an existing value" do
#hash[:foo].should == "bar"
end
it "should get nil for a value that doesn't exist" do
#hash[:bad_key].should be_nil
end
it "should allow lookup of multiple keys, returning an array" do
#hash[:foo, :yin, :bad_key].should == ["bar", "yang", nil]
end
end
end
You end up with sentences like:
Redis::BigHash#[] should read an existing value.
Redis::BigHash#[] should get nil for a value that doesn't exist.
Redis::BigHash#[] should allow lookup of multiple keys, returning an array.
Just simple English sentences that describe the behavior you want.
Related
This question already has answers here:
RSpec matcher that checks collection to include item that satisfies lambda
(2 answers)
Closed 3 years ago.
I want to assert that an array contains at least one element that passes an RSpec expectation. But most of the elements in the array will not pass the expectation. So I'd like to do something like this:
it "finds one element that matches" do
array.any? do |element|
expect(element).to eq("expected value")
end
end
And have the test pass if any of the elements pass the expectation. But, of course, the test will fail as I've written it here.
Is there a pattern in RSpec to do what I want to accomplish?
I don't want to do this:
it "finds one element that matches" do
expect(array.any? {|val| val == "expected value"}).to be_true
end
Because it's not clear to me how to manually check the same thing as the matcher I need to use in my test. I want to use the have_attributes matcher, which does some subtle metaprogramming magic I don't want to risk messing up trying to re-implement on my own.
You can use the include matcher to compose matchers:
expect(array).to include(a_string_matching(/foo/))
Despite the somewhat awkward syntax, you can use this with have_attributes:
expect(obj).to have_attributes(tags: include(a_string_matching(/foo/))
But if that's not flexible enough for whatever reason, you can use the satisfy matcher:
expect(array).to satisfy {|arr| arr.any? {|val| val == "expected value"})
The double-nested block is somewhat awkward by itself, but the flexibility of satisfy lets you do all kinds of stuff with it, and you can use the include matcher to get you there. For example:
require "rspec"
require "ostruct"
obj = OpenStruct.new(name: "foobar", tags: %w(bin bazzle))
describe obj do
it "has a bin tag" do
is_expected.to have_attributes(tags: include(/bin/))
end
it "has a tag 3 characters long" do
is_expected.to have_attributes(tags: include(satisfy { |t| t.length == 3 }))
end
end
If you're willing to add a gem, I really like rspec-its for cases like these: they can clean up the specs for individual attributes of an object which don't warrant their own subject block nicely:
describe obj do
its(:tags) { is_expected.to be_a Array }
its(:tags) { is_expected.to include "bin" }
end
RSpec has composing matchers that can be used with expect(array).to include to achieve what I wanted. For example:
expect(array).to include(a_string_matching("expected value"))
For the have_attributes matcher specifically, RSpec has an alias for it called an_object_having_attributes, allowing me to write:
expect(array).to include(an_object_matching(object_i_want_duplicated))
Im new to testing in ruby with Rspec. I'm just wanting to write a simple test to see if the below code works. Im not sure how to do it. The code returns an acronym of a given string. thanks
def acronym(sentence)
first_letters = []
sentence.split.each do |word|
first_letters << word[0]
end
first_letters.join
end
describe "acro method" do
it "returns acronym of words" do
end
end
Define Your Input and Expected Output
The point of TDD is to test expected behavior. To construct a test, you must define both your fixture (a known input value) and your expectation (the output you expect your method to produce given a known input value). You then compare the results of your spec with a suitable matcher. For example:
def acronym(sentence)
first_letters = []
sentence.split.each do |word|
first_letters << word[0]
end
first_letters.join
end
describe "#acronym" do
let(:sentence) { 'A very short sentence.' }
it "returns initial letter of each word" do
expect(acronym sentence).to eq('Avss')
end
end
When you run the spec in document format, it should read fairly naturally.
$ rspec --format doc foo_spec.rb
#acronym
returns initial letter of each word
Finished in 0.0017 seconds (files took 0.12358 seconds to load)
1 example, 0 failures
If you change your test's expected output from Avss to avss, then your expectation will fail. A well-written test will give you a useful error like:
Failures:
1) #acronym returns initial letter of each word
Failure/Error: expect(acronym sentence).to eq('avss')
expected: "avss"
got: "Avss"
(compared using ==)
You can then fix your class or method until the desired behavior is achieved.
Use RSpec matchers to check that what your method outputs actually matches what you expect it to do.
https://relishapp.com/rspec/rspec-expectations/docs/built-in-matchers
describe "acro method" do
it "returns acronym of words" do
test_sentence = "this is a test acronym"
expected_acronym = "tiata"
expect(acronym(test_sentence)).to eq(expected_acronym)
end
end
I have specs that have:
describe "can translate" do
let(:from){591}
it "#{from}" do
expect(Translator.three_digits(from)).to eq 'five hundred ninety two'
end
end
but 591 is hard-coded and repeated which I want to eliminate...
so how can I refer to from in the it description?
I tried having let(:from){591}
and then using it "#{#from}" but that doesn't show it
I also tried using it "#{from}" but that gives an error undefined local variable or method
'from' for #<Class:0x00000001bc4110> (NameError) as it's looking for a locally scoped variable.
I can avoid all these scope issues with constants, i.e.
describe "can translate" do
FROM=592
it "#{FROM}" do
expect(Translator.three_digits(FROM)).to eq 'five hundred ninety two'
end
end
With that when I get an eror I actually get A ruby file called translator.rb can translate 591 (or whatever number, the key this is that it prints out unlike all my attempt with the variable).
`
but this seems like a poor approach. I prefer to avoid constant when possible and I want to do this test for several values in a row, so I need something I can change from case to case and a CONSTANT seems inappropriate.
I also tried before :all with both local and instance variables but with no success.
If I hard code the it and literally put 591 as the text and the test fails then the 591 prints out which is what I want. However I cannot get the same result working though any variable that I also use in the test.
If you want to run same test for several values, you can do:
values = [100,200,300]
values.each do |value|
it "#{value} do
... # use value here
end
end
The reason why you couldn't do this the way you tried was that it is a class method, and lets defines an instance method. Also note, that if you use let multiple times you will override previous method definition with new one. Since rspec first reads all the tests definitions and then executes them, they all will be run with the same method defined with let. Hence this will not work as expected:
values = [100,200,300]
values.each do |value|
let(:from) { value }
it "#{value} do
puts from
end
end
The above will input 300 three times.
You can add Ruby code within the describe block to define a collection which then can be enumerated to produce multiple examples, e.g.:
describe "can translate" do
translations = [
{input: 591, output: "five hundred ninety one"},
{input: 592, output: "five hundred ninety two"}
]
translations.each do |t|
context "when input is #{t[:input]}" do
it "translates to #{t[:output]}" do
expect(Translator.three_digits(t[:input])).to eq t[:output]
end
end
end
end
I'm trying (and succeeding with) local variables like this:
...
describe "can translate" do
from=738
it from do
expect(Translator.three_digits from).to eq 'seven hundred thirty eight'
end
end
describe "can translate" do
from=592
it from do
expect(Translator.three_digits(from)).to eq 'five hundred ninety two'
end
end
...
describe "can translate" do
subject { Translator.three_digits(from) }
let(:from){|e| e.description.to_i}
it "592" do
is_expected.to eq 'five hundred ninety two'
end
# or
specify("593"){ is_expected.to eq 'five hundred ninety three' }
end
I am trying to write fast and concise code. I'd appreciate your thoughts on which is the best way to write the following code and why:
Option #1
def get_title
title = check_in_place_one
if title.empty?
title = check_in_place_two
if title.empty?
title = check_in_place_three
end
end
return title
end
Option #2
def get_title
title = check_in_place_one
title = check_in_place_two unless !title.empty?
title = check_in_place_three unless !title.empty?
return title
end
I think Option #1 is better since if the title is found by check_in_place_one, we test title.empty? once and then skip the rest of the code in the method and return. But, it looks too long. Option #2 appears better, but processes title.empty? one extra time, and unnecessary time before returning. Also, am I missing a third option?
From performance, there is no difference between the two versions of your code (besides very minor difference that may come from parsing, which should be ignorable). The control structures are the same.
From readability, if you can get away with nesting, doing so is better. Your second option is better.
It is usually better to get rid of any case that does not need further processing. That is done by return.
def get_title
title = check_in_place_one
return title unless title.empty?
title = check_in_place_two
return title unless title.empty?
title = check_in_place_three
return title
end
The last title = and return in the code above are redundant, but I put them there for consistency, which improves readability.
You can further compact the code using tap like this:
def get_title
check_in_place_one.tap{|s| return s unless s.empty?}
check_in_place_two.tap{|s| return s unless s.empty?}
check_in_place_three
end
tap is a pretty much fast method, and unlike instance_eval, its performance penalty is usually ignorable.
The following approach could be used for any number of sequential tests. Moreover, it is completely general. The return condition could be changed, arguments could easily be assigned to the test methods, etc.
tests = %w[check_in_place_one check_in_place_two check_in_place_three]
def do_tests(tests)
title = nil # Define title outside block
tests.each do |t|
title = send(t)
break unless title.empty?
end
title
end
Let's try it:
def check_in_place_one
puts "check 1"
[]
end
def check_in_place_two
puts "check 2"
''
end
def check_in_place_three
puts "check 3"
[3]
end
do_tests(tests) #=> [3]
check 1
check 2
check 3
#=> [3]
Now change one of the tests:
def check_in_place_two
puts "check 2"
'cat'
end
do_tests(tests) #=> 'cat'
check 1
check 2
#=> "cat"
If there were more tests, it might be convenient to put them in a module which would be included into a class. Mixed-in methods behave the same as those that you define for the class. For example, they have access to instance variables. I will demonstrate that with the definition of the first test method. We probably want to make the test methods private. We could do it like this:
module TestMethods
private
def check_in_place_one
puts "#pet => #{#pet}"
puts "check 1"
[]
end
def check_in_place_two
puts "check 2"
''
end
def check_in_place_three
puts "check 3"
[3]
end
end
class MyClass
##tests = TestMethods.private_instance_methods(false)
puts "##tests = #{##tests}"
def initialize
#pet = 'dog'
end
def do_tests
title = nil # Define title outside block
##tests.each do |t|
title = send(t)
break unless title.empty?
end
title
end
include TestMethods
end
The following is displayed when the code is parsed:
##tests = [:check_in_place_one, :check_in_place_two, :check_in_place_three]
Now we perform the tests:
MyClass.new.do_tests #=> [3]
#pet => dog
check 1
check 2
check 3
Confirm the test methods are private:
MyClass.new.check_in_place_one
#=> private method 'check_in_place_one' called for...'
The advantage of using a module is that you can add, delete, rearrange and rename the test methods without making any changes to the class.
Well, here's a few other alternatives.
Option 1: Return first non-empty check.
def get_title
return check_in_place_one unless check_in_place_one.empty?
return check_in_place_two unless check_in_place_two.empty?
return check_in_place_three
end
Option 2: Helper method with short-circuit evaluation.
def get_title
check_place("one") || check_place("two") || check_place("three")
end
private
def check_place(place)
result = send("check_in_place_#{place}")
result.empty? ? nil : result
end
Option 3: Check all places then find the first that's non-empty.
def get_title
[
check_in_place_one,
check_in_place_two,
check_in_place_three,
].find{|x| !x.empty? }
end
Option 2 looks good although you did a 360 degree turn with the unless !title.empty?. You can shorten that to if title.empty? since unless is equivalent to an if ! so doing an unless ! takes you back to just if.
If you're only ever going to have 3 places to look in then option 2 is the best. It's short, concise, and easy to read (easier once you fix the aforementioned whoopsie). If you might add on to the places you look for a title in you can get a bit more dynamic:
def get_title(num_places = 4)
title, cur_place = nil, 0
title = check_in_place(cur_place += 1) while title.nil? && cur_place < num_places
end
def check_in_place(place_num)
# some code to check in the place # given by place_num
end
The tricky line is that one with the while in it. What's happening is that the while will check the expression title.nil? && cur_place < num_places and return true because the title is still nil and 0 is less than 4.
Then we'll call the check_in_place method and pass in a value of 1 because the cur_place += 1 expression will increment its value to 1 and then return it, giving it to the method (assuming we want to start checking in place 1, of course).
This will repeat until either check_in_place returns a non nil value, or we run out of places to check.
Now the get_title method is shorter and will automatically support checking in num_places places given that your check_in_place method can also look in more places.
One more thing, you might like to give https://codereview.stackexchange.com/ a look, this question seems like it'd be a good fit for it.
I don't think there's any reason to get too clever:
def get_title
check_in_place_one || check_in_place_two || check_in_place_three
end
Edit: if the check_in_place_X methods are indeed returning an empty string on failure it would be better (and more idiomatic) to have them instead return nil. Not only does it allow for truthy comparisons like the above code, return "" results in the construction of a new and unnecessary String object.
I'm new to Rspec and trying to have Rspec create new tests for something variable. Here's what I've tried:
require 'rspec'
describe 'a test' do
#array = []
before(:all) do
#array = [1,3,4,6,9,2]
end
#array.each do |i|
it { i.should > 3 }
end
it { #array.should have(4).items }
end
Unfortunately, the array doesn't seem to get filled before the .each block runs. Is there a way to make it work?
The before block doesn't get executed until just before RSpec starts to execute the it blocks. But you're iterating through #array in the outer body of describe, so #array is still nil at that point. Within the last it block, #array has been initialized.
You can put pretty much any code you want within an it block. For example, you could write:
it "should have elements > 3" do
#array.each do |i|
i.should > 3
end
end
Or, if you want separate it calls, you can just populate #array in the describe block itself, as in:
describe 'a test' do
#array = [1,3,4,6,9,2]
#array.each do |i|
it { i.should > 3 }
end
it { #array.should have(4).items }
end
although you might want to rework it in this case so that you pass a string argument to it indicating which element of the array (i.e. what index) you're operating on.
As for dynamically generating it statements based on data defined in the let/before hierarchy, I don't think that's possible, because you only have access to that data within an it block and it is not part of the acceptable DSL within an it block.
Here's the best that I could come up with so far. It's dirty, but if generating tests while iterating an array that isn't pre-defined is what you're after, then you can try to loop a number that you guess will overshoot the filled indexes.
require 'rspec'
describe 'a test' do
before(:all) do
#array = [1,3,4,6,9,2]
end
16.times do |i|
it 'has a desc' do
next if #array[i].nil?
#array[i].should > 3
end
end
end
The caveat here is how you're gonna deal with the tests for indexes past the array's filled index. So far, I've found using next to be the cleanest as it just results in another passed test. using return or break results in proc-closure or jump errors. I also tried wrapping my method in a begin,rescue block but it seemed it couldn't capture any errors within the it block.
Not sure if you managed to come up with a better answer, but wondering if this would solve the problem for you:
RSpec.describe 'a test' do
array = (1..100).to_a.sample(6)
array.each do |i|
it 'has a desc' do
expect(i).to be > 3
end
end
end
I think the answer depends on how/why you want the data to vary.