There are some articles (e.g. [1]) regarding solving flaky acceptance tests when using Capybara which advocates using e.g.
.to have_text("foo")
instead of
.to eql("foo")
In one of my tests I have .to match(/foo/) and every once in a while this fails. I assume that the match matcher is not in the same category as e.g. the have_text matcher and doesn't wait. The documentation doesn't mention anything regarding this.
Is there any regex matcher so that I can check e.g.
expect(next_url).to match(/foo/)
?
Versions used (not changeable):
capybara: 2.7.x
spec-rails: 3.6.x
[1] https://www.urbanbound.com/make/fix-flaky-feature-tests-by-using-capybaras-apis-properly
The docs for have_text link to the assert_text docs - https://www.rubydoc.info/gems/capybara/Capybara/Node/Matchers#assert_text-instance_method which show that it takes either a string
expect(page).to have_text('Something')
or a Regexp
expect(page).to have_text(/foo/)
As the article you linked to implies, if you find yourself using any non capybara provided matcher with information returned from Capybara you're probably doing something wrong, and setting yourself up for flaky tests.
If you have a page where elements have a delay appearing on the page, you can define a 'wait' method in 'capybara_helpers.rb'
def wait_for timeout = 10, &block
Timeout.timeout(timeout) do
loop do
condition = yield
if (condition)
break true
end
end
end
rescue Timeout::Error
raise "Condition not true in #{timeout} seconds"
end
After that, you can use 'wait_for' method like this:
wait_for { page.has_css?('.class', text: 'Something') }
Related
When a run a test with JSON, the rspec doesn't show the full spec, so I can't see the diference between return and expected.
The message of diff is shortened with ...
expected: "{\"id\":1,\"number\":1,\"sequential\":1,\"emitted_at\":\"2014-01-01T13:35:21.000Z\",\"status\":\"aut...erenceds_attributes\":[{\"id\":null,\"nfe_key\":\"42150707697707000148550010000020101000020105\"}]}"
got: "{\"id\":1,\"number\":1,\"sequential\":1,\"emitted_at\":\"2014-01-01T13:35:21.000Z\",\"status\":\"aut...erenceds_attributes\":[{\"id\":null,\"nfe_key\":\"42150707697707000148550010000020101000020105\"}]}"
aut...erenceds_attributes look in middle of message
My script test:
RSpec.describe InvoiceSerializer do
let(:invoice) do
build :invoice, :testing_serializer
end
subject { described_class.new invoice }
it "returns a json" do
expected = {
id: 1,
number: 1,
sequential: 1,
emitted_at: "2014-01-01T13:35:21.000Z",
status: "authorized",
invoice_bills_attributes: [{
id: nil,
expire_at: "2014-01-02T00:00:00.000Z",
value: "1.23"
}],
...
}.to_json
expect(subject.to_json).to eq expected
end
end
Example of error in my console
What gem/plugin or expectation that you use to check your test?
I use the console and Rubymine IDE.
Now I use:
puts "1 --> #{subject.to_json}"
puts "2 --> #{expected}"
And I don't like to write this for to debbug my test.
Set RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length to a high value
Update: as Yurri suggested, it might be better to better to set it to nil
This might help: https://github.com/waterlink/rspec-json_expectations
As a bonus, it allows you to specify your tests in terms of a subset of attributes, which can be used to create more granular tests.
To build on previous answers, and utilize the RSpec.configure syntax you'll want to use something like this:
RSpec.configure do |rspec|
rspec.expect_with :rspec do |c|
# Or a very large value, if you do want to truncate at some point
c.max_formatted_output_length = nil
end
end
I am a freshman on Rspec
it "should not ask the database" do
#sqlite_database.should_not_receive(:findISBN)
#result = #cache.findISBN('1234')
#result.should eql(#book)
end
it "should not ask the database" do
#sqlite_database.should_not_receive(:authorSearch)
#result = #cache.authorSearch('author')
#result.should eql(#book)
end
Here are two different part 1,:findISBN and :authorSearch 2 findISBN('1234') and authorSearch('author')
I try to use let but it doesn't work ,who can help ?
#sqlite_database = double()
#cache = SQLiteDataBaseWithCache.new(#sqlite_database)
That's ture ,I'm coming from java background .You coding showed some warning: syntax error, unexpected ':', expecting keyword_end (SyntaxError).I have no ideal about it
without more details on the variables like #sqlite_database, #cache etc its hard to tell whats happening, but following should work
probably a stack trace would help
following is the idea of shared examples :)
shared_examples "it should not ask database" do |symbol, params|
it "should not ask the database" do
#sqlite_database.should_not_receive(symbol)
#result = #cache.send symbol, params
#result.should eql(#book)
end
end
it_behaves_like "it should not ask the database", :findISBN, '1234'
it_behaves_like "it should not ask the database", :authorSearch, 'author'
and on a side note, your method signatures are not ruby kind of... in ruby we normally dont use camel case
so in ruby/rails its should be author_search, probably you are coming from java background :)
I had the same problem as Nick Rutten in this thread Rails Tutorial: RSpec test decoupling. I suspected the problem was the FactoryGirl.create() saving to the database, and thanks to #prusswan I fixed it.
However I do not understand why these puts don't indicate different User.count.
This is the snippet I'm interested in:
describe "with valid information" do
puts "Number of users pre-Factory:" + User.count.to_s
let(:user){FactoryGirl.create(:user)}
puts "Number of users post-Factory:" + User.count.to_s
(...)
end
I get:
Number of users pre-Factory:0
Number of users post-Factory:0
But shouldn't I get instead?:
Number of users pre-Factory:0
Number of users post-Factory:1
Btw, the factory is defined (although not so relevant now) as:
FactoryGirl.define do
factory :user do
name "Example User"
email "user#example.com"
password "foobar"
password_confirmation "foobar"
end
end
I'm stuck trying to understand why the counter is not increasing, wasn't the problem that it did increase to start with?
thank you
Edit using #derekyau's suggestions:
Right now I've got in this order:
it "print counter before let"
puts "Number of users:" + User.count.to_s
end
let(:user){FactoryGirl.create(:user)}
it "print counter after let"
puts "Number of users:" + User.count.to_s
end
However now I get both values 1:
Number of users:1
Number of users:1
Close but not there yet!
Edit 2
As explained by #Peter Alfvin here: Within a given RSpec describe block, does the order of let, before and it statements matter? . The order of let, before, it is pretty much established, regardless of the code. That, together with #derekyau's explanation closes the question.
Good question, I think that let(:user) is lazily evaluated so it isn't actually called until you use it inside of an it block.
If you want to force it to be evaluated try let!
let!(:user) {FactoryGirl.create(:user)}
If you want to see it change try doing the puts inside of an "it block"
it "check user count" do
puts User.count
end
You should see '1'
I just upgraded to RSpec 2.13 and am seeing two different warnings when running my specs:
/Users/peterbrown/.rvm/gems/ruby-2.0.0-p0/gems/rspec-core-2.13.0/lib/rspec/core/memoized_helpers.rb:120: warning: method redefined; discarding old subject
/Users/peterbrown/code/classy_enum/spec/classy_enum/active_record_spec.rb:63: warning: previous definition of subject was here
I suspect it has something to do with how I am looping over an array and defining a new subject each time:
describe DefaultDog do
context "with valid breed options" do
[:golden_retriever, 'golden_retriever', Breed::GoldenRetriever.new, Breed::GoldenRetriever].each do |option|
subject { DefaultDog.new(:breed => option) }
it { should be_valid }
its(:breed) { should be_a(Breed::GoldenRetriever) }
end
end
end
Is there a problem with defining a subject in a loop like this?
Update:
I should also note that I'm running my specs with the -w option:
RSpec::Core::RakeTask.new(:spec) do |t|
t.ruby_opts = "-w"
end
Try putting the context within your loop:
describe DefaultDog do
[:golden_retriever, 'golden_retriever', Breed::GoldenRetriever.new, Breed::GoldenRetriever].each do |option|
context "with valid breed option #{option}" do
subject { DefaultDog.new(:breed => option) }
it { should be_valid }
its(:breed) { should be_a(Breed::GoldenRetriever) }
end
end
end
I believe that writing tests that involve iterating over collections this way means that you will execute a separate test for every item in the collection, versus just performing one test that loops through a collection (hence redefining the subject). I think this is an understated gotcha in RSpec that caused me much confusion until fairly recently.
I've run across a couple examples where people test for the content of an html element with page.should have_selector "title", content: "my awsome content", but this seems to always pass. The correct call seems to be page.should have_selector "title", text: "my awsome content" (notice text: instead of content:).
What is the content: selector for in this Rspec call?
Short answer:
have_selector doesn't support a :content option.
Long answer:
Rspec creates dynamic methods that translates calls like [].should be_empty to [].empty?.should == true and, in your case, page.should have_selector "title", content: "my awesome content" to page.has_selector?("title, content: "my awesome content").should == true.
Capybara provides the has_selector? method, which basically just passes its parameters to its test/unit style assertion, assert_selector:
def has_selector?(*args)
assert_selector(*args)
rescue Capybara::ExpectationNotMet
return false
end
assert_selector then passes its parameters again to the Capybara finder method, all(), returning true if it found any matches and raising an exception if it doesn't (which has_selector? catches and then returns false with).
def assert_selector(*args)
synchronize do
result = all(*args)
result.matches_count? or raise Capybara::ExpectationNotMet, result.failure_message
end
return true
end
The all method returns all of the results that match the query. This is where we'll find the documentation on acceptable options:
# #overload all([kind], locator, options)
# #param [:css, :xpath] kind The type of selector
# #param [String] locator The selector
# #option options [String, Regexp] text Only find elements which contain this text or match this regexp
# #option options [Boolean] visible Only find elements that are visible on the page. Setting this to false
# (the default, unless Capybara.ignore_hidden_elements = true), finds
# invisible _and_ visible elements.
# #return [Array[Capybara::Element]] The found elements
We can see that content is not listed as a valid options, so is just ignored.