How to keep en.yml DRY? - ruby-on-rails

I have a en.yml which contains the following
en:
article:
title:
format: "Requires at least one alphanumeric character"
title_format: "Title Requires at least one alphanumeric character"
The article.title.format is used in my article model and article.title_format is used in article_test test. Is there any way to make this DRY?

YAML is limited for things like this, but you do have anchors, aliases and merge_keys. This SO answer is VERY exhaustive on this issue.
So, you can repeat the exact same message, but not modify that message without some other library or code injection:
&standard_answer: "Requires at least one alphanumeric character"
en:
article:
title:
format: *standard_answer
title_format: *standard_answer
in Ruby, is equivalent to:
en:
article:
title:
format: "Requires at least one alphanumeric character"
title_format: "Requires at least one alphanumeric character"
To answer the question you aren't asking...
If you are trying to test model validations, I think you're doing too much anyway:
These will break if Rails decides to tweak the language used
Your tests now rely on your i18n file just to find a hard-coded string
It's less readable because the test constraints are now in 2 separate files (but it is more compact, so you could argue that)
What you want to know is: "Can I save this record without any alphanumeric characters in the title?"
I would only test whether the validation succeeds or fails.
If you're using RSpec, I like this pattern.
Or, another option, RSpec Expectations ships with Predicate matchers:
# prove the record is valid
expect(record).to be_valid
# make the record invalid in only 1 way
record.title = 'does not have a number'
expect(record).to be_invalid # also works: expect(record).not_to be_valid
Note that I'm making sure the record is valid first, then changing one thing, then checking for validity again. This helps ensure you are testing the right validation.

Related

rspec nested context strings concatenation

in my rspec tests i have quite a few deep nested contexts like these:
describe "#mymethod"
it_behaves_like "specific object"
end
shared_examples_for "specific object" do
context "when it receives proper parameters" do
context "when file is in a queue" do
it "behaves as i want it to" do
#...
end
end
end
as a result when i run my rspec tests i get results like those (or more complex) in sections "Pending"/"Failures"/"Failed examples":
mymethod behaves like specific object when it receives proper parameters when file is in a queue behaves as i want it to
those context strings sometimes tend to be unreadable for the lack of separators.
how can i monkey patch rspec to add them (f.ex. "|") automatically (or is there another preferred solution)? where do descriptor strings get concatenated in rspec?
RSpec has the notion of a separator hard-coded. Unfortunately, it is not configurable right now, but I guess the maintainers would consider a patch for review.

Keeping rspec convention with --format doc

Betterspecs suggests using something like:
subject { assigns('message') }
it { should match /it was born in Billville/ }
as good practice. But in case i want to run rspec in doc format (rspec -f doc) i'm receiving:
When you call a matcher in an example without a String, like this:
specify { object.should matcher }
or this:
it { should matcher }
RSpec expects the matcher to have a #description method. You should either
add a String to the example this matcher is being used in, or give it a
description method. Then you won't have to suffer this lengthy warning again.
So this
it "some desc" do
should match /it was born in Billville/
end
won't raise that annoying message but seems ugly.
Any ideas on how to keep rspec conventions and code clean, and still have some pretty output(like with -f doc)?
rspec v.2.13.0
As RSpec maintainer, there are many things listed on betterspecs.org with which I disagree. I've commented as such on the github issues for the project many months ago, but sadly, I don't think any of my concerns have been addressed :(.
Anyhow, I think the one-liner syntax is fine to use when the doc output matches what you want, but often it does not. Usually, the doc output of the one-liner syntax is overly specific, e.g. it returns in doc strings like should eq "dfgh" even though that is not a generally true behavior -- something like returns a string without vowels removed is a better, more generally true description of the behavior.
So my suggestion is to not use the one-liner syntax unless it produces the output you want. Don't use it just because betterspecs.org recommends it. Many of its recommendations are bad recommendations, in my opinion.
Personally, I agree with BetterSpecs on this. What is so difficult to understand about the following?
subject { assigns('message') }
it { should match /it was born in Billville/ }
If, as #Myron Marston opines, that it doesn't produce good enough output, then I'd suggest (and I always do) using contexts, e.g.
context "When given a message" do
subject { my_func arg }
context "With vowels" do
let(:arg) { "dafegih" }
let(:expected){ "dfgh" }
it { should == expected }
end
context "Without vowels" do #…
You'll get lovely output, and it also reads well as code, is terse, and encourages you to think about the different inputs, and reuse via shared examples and contexts that then encourages a test across a wider range of inputs. Using the string + block based way of writing specs encourages several specs to be crammed into one test, e.g.
it "some desc" do
should_not be_nil
should match /it was born in Billville/
end
If that fails was it because it was nil, or because it didn't match? Will that encourage reuse? This is far better, IMO:
subject { assigns('message') }
it{ should_not be_nil }
it { should match /it was born in Billville/ }
It's nice that the writers and maintainers of libraries intend for us to use libraries well, but I'm getting a bit tired of them thinking they can nag us or force us to do these things. RSpec has added itself to the list that includes Bundler and Rails headed "Projects that are really useful and I'm really grateful for, but should butt out of my business".

Is it the right way to output more information about tests that are going to be run?

I am using Ruby on Rails 3.2.2 and rspec-rails-2.8.1. I would like to output more information about tests that are going to be run, for example, this way:
# file_name.html.erb
...
# General idea
expected_value = ...
it "... #{expected_value}" do
...
end
# Usage that I am trying to implement
expected_page_title =
I18n.translate(
'page_title_html'
:user => #user.firstname
)
it "displays the #{expected_page_title} page title" do
view.content_for(:page_title).should have_content(expected_page_title)
end
Note: "Outputs" are intended to be those that are output when you run the rspec . --format documentation command line in the Terminal window.
Is it a right way to test?
Related questions:
How to use an instance variable throughout an Example Group, even if it is outside a Example?
Your question is going to solicit some opinions, but I'll try and justify mine with some examples.
Short answer: No, this isn't how you should be writing RSpec (or any test) descriptions. It's unconventional and doesn't add much value for the extra code.
Long answer: RSpec is a BDD (behavior driven development) tool that was designed to help describe the behavior and intent of your code at the same time as writing automated tests. When you think about the behavior of your code, does adding the expected result to the test description really add much value? If so, maybe you should rethink what you are testing.
For example, say you have a User class and you want to test a method that concatenates a user's first and last name:
describe User do
expected_full_name = 'Software Guy'
subject { User.new(first: 'Software', last: 'Guy') }
it 'should have the full name #{expected_full_name}' do
subject.full_name.should == 'Software Guy'
end
end
VS
describe User do
subject { User.new(first: 'Software', last: 'Guy') }
it 'should have a full name based on the first and last names' do
subject.full_name.should == 'Software Guy'
end
end
In the first test, what does having the expected result in the description really buy you? Does it tell you anything about the expected behavior of a user? Not really.
Take your example. If I was coming along to your project and saw a test description like that, I would be confused because it doesn't really tell me what is being tested. I would still need to look at the code to understand what is going on. Compare these two examples:
it "displays the #{expected_page_title} page title" do
view.content_for(:page_title).should have_content(expected_page_title)
end
Which would give you something in the console like:
"displays the My Awesome Title page title"
Compare that to:
it "should translate the page title" do
view.content_for(:page_title).should have_content(expected_page_title)
end
Which would be exactly the same in the console as it is in the test:
"should translate the page title"
Your obviously free to choose whichever one you want, but I am speaking from a few years of testing experience and highly recommend you don't do this.

Railstutorial.org Validating unique email

In section 6.2.4 of Ruby on Rails 3 Tutorial, Michael Hartl describes a caveat about checking uniqueness for email addresses: If two identical requests come close in time, request A can pass validation, then B pass validation, then A get saved, then B get saved, and you get two records with the same value. Each was valid at the time it was checked.
My question is not about the solution (put a unique constraint on the database so B's save won't work). It's about writing a test to prove the solution works. I tried writing my own, but whatever I came up with only turned out to mimic the regular, simple uniqueness tests.
Being completely new to rspec, my naive approach was to just write the scenario:
it 'should reject duplicate email addresses with caveat' do
A = User.new( #attr )
A.should be_valid # always valid
B = User.new( #attr )
B.should be_valid # always valid, as expected
A.save.should == true # save always works fine
B.save.should == false # this is the problem case
# B.should_not be_valid # ...same results as "save.should"
end
but this test passes/fails in exactly the same cases as the regular uniqueness test; the B.save.should == false passes when my code is written so that the regular uniqueness test passes and fails when the regular test fails.
So my question is "how can I write an rspec test that will verify I'm solving this problem?" If the answer turns out to be "it's complicated", is there a different Rails testing framework I should look at?
It's complicated. Race conditions are so nasty precisely because they are so difficult to reproduce. Internally, save goes something like this:
Validate.
Write to database.
So, to reproduce the timing problem, you'd need to arrange the two save calls to overlap like this (pseudo-Rails):
a.validate # first half of a.save
b.validate # first half of b.save
a.write_to_db # second half of a.save
b.write_to_db # second half of b.save
but you can't open up the save method and fiddle with its internals quite so easily.
But (and this is a big but), you can skip the validations entirely:
Note that save also has the ability to skip validations if passed :validate => false as argument. This technique should be used with caution.
So if you use
b.save(:validate => false)
you should get just the "write to database" half of b's save and send your data to the database without validation. That should trigger a constraint violation in the database and I'm pretty sure that will raise an ActiveRecord::StatementInvalid exception so I think you'll need to look for an exception rather than just a false return from save:
b.save(:validate => false).should raise_exception(ActiveRecord::StatementInvalid)
You can tighten that up to look for the specific exception message as well. I don't have anything handy to test this test with so try it out in the Rails console and adjust your spec appropriately.

Rails assert that form is valid

What's the best practices way to test that a model is valid in rails?
For example, if I have a User model that validates the uniqueness of an email_address property, how do I check that posting the form returned an error (or better yet, specifically returned an error for that field).
I feel like this should be something obvious, but as I'm quickly finding out, I still don't quite have the vocabulary required to effectively google ruby questions.
The easiest way would probably be:
class UserEmailAddressDuplicateTest < ActiveSupport::TestCase
def setup
#email = "test#example.org"
#user1, #user2 = User.create(:email => #email), User.new(:email => #email)
end
def test_user_should_not_be_valid_given_duplicate_email_addresses
assert !#user2.valid?
end
def test_user_should_produce_error_for_duplicate_email_address
# Test for the default error message.
assert_equal "has already been taken", #user2.errors.on(:email)
end
end
Of course it's possible that you don't want to create a separate test case for this behaviour, in which case you could duplicate the logic in the setup method and include it in both tests (or put it in a private method).
Alternatively you could store the first (reference) user in a fixture such as fixtures/users.yml, and simply instantiate a new user with a duplicate address in each test.
Refactor as you see fit!
http://thoughtbot.com/projects/shoulda/
Shoulda includes macros for testing things like validators along with many other things. Worth checking out for TDD.
errors.on is what you want
http://api.rubyonrails.org/classes/ActiveRecord/Errors.html#M002496
#obj.errors.on(:email) will return nil if field is valid, and the error messages either in a String or Array of Strings if there are one or more errors.
Testing the model via unit tests is, of course, step one. However, that doesn't necessarily guarantee that the user will get the feedback they need.
Section 4 of the Rails Guide on Testing has a lot of good information on functional testing (i.e. testing controllers and views). You have a couple of basic options here: check that the flash has a message in it about the error, or use assert_select to find the actual HTML elements that should have been generated in case of an error. The latter is really the only way to test that the user will actually get the message.

Resources