Consider this example, we'd like to check whether it's allowed for user to have blank name (it shouldn't be):
test "name should be present" do
#user.name = " "
assert_not #user.valid?
end
The question is why does assert_not exists? Wouldn't it be better if we just use assert like this:
test "name should be present" do
#user.name = " "
assert !#user.valid?
end
It likely exists to remove modifiers from your assertions, which may change their results or obscure what you're actually asking. In reality, it's mostly a style choice.
It's kind of the same motivation for having unless in the language, instead of writing this:
if !#user.valid?
# do stuff
end
You would do:
unless #user.valid?
# do stuff
end
Granted, the if/unless differences read way better than assert_not, alas that's what Minitest unit tests are going to get you. If you want things to read more naturally, take a look at Minitest specs or RSpec.
Related
tl;dr Valid names don't get written to the database because the test fails, and invalid names do get written to the database because the test passes.
Edit: For clarification about the project and my question in general: As described in the book, this User model is set up as the beginning stages to allow a website user to eventually log in to a website. The database columns would be "name" and "email", and each row would be one user (assuming the user name and email were valid). I've edited my original post below for further clarification, and all edits are in italics.
Additionally, please only respond if you can explain the code as is in my post--don't suggest adding additional code to make it work. The textbook I'm working from asserts that this code should work as is, yet it seems to evaluate to the opposite that it should. Lastly, if you know other links that explain this in more detail, that is helpful; however, I have already read apidock, RoR API, and most of the SO links that show up in a Google search, and none have been helpful in explicating this problem.
I'm working through Michael Hartl's Ruby on Rails Tutorial. I'm in chapter 6 working through validation tests and I'm stuck on the validation for the presence of a name. It seems to be doing exactly the opposite of what the tutorial says it should do (i.e., validating a non-valid name entry), even though I've followed the tutorial exactly. I've also searched the web and SO for more details on how assert_not works, but can't find any helpful details. Here's the process I've went through.
Add a test that we know will fail:
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
#user = User.new(name: "Example User", email: "user#example.com")
end
# resolves to true and passes
test "should be valid" do
assert #user.valid?
end
test "name should be present" do
#user.name = " "
assert_not #user.valid?
end
end
So far so good. I understand this code perfectly well, and although the second test's wording is awkward, I interpret assert_not #user.valid? to work like this: #user.valid? evaluates to true, since no validations have been set up, so the test thinks a name with all blank spaces is valid. This is preceded by an assert_not which converts a true value to false, and makes the test fail.
We add a validation test:
class User < ActiveRecord::Base
validates :name, presence: true
end
This sets a validation rule for the User model that ensures the :name object has content and is not nil, an empty string, or blank space. Still good so far.
Here's where things get tricky. At this point, the expression #user.valid? in our test should (and does) evaluate to false, since the validation rule won't allow a :name with only spaces in it. I've added comments alongside this code to help my mind see what values are assigned and evaluated in this process:
test "name should be present" do
#user.name = " "
# => name: " " email: "example#example.com"
assert_not #user.valid?
# => assert_not false => true
end
#user.valid? evaluates to false, which makes the entire assert_not expression evaluate to true, and thus the test passes. In pseudocode, this line could translate thusly: assert_not (is the user valid? no) ==> assert_not(false) ==> true. Similarly, "We do not assert that the user is false", which evaluates to true.
This is where my problem is: The test passes on an invalid value for :name, thus allowing a name consisting of blank spaces to be written to the database, when that's exactly the opposite of what we are trying to achieve, namely, preventing blank names from being written to db.
Conversely, I worked out this logic with a valid name:
test "name should be present" do
# => name: "Example User" email: "example#example.com"
assert_not #user.valid?
# assert_not true => false
end
In this case, since #user.valid? evaluates to true (since "Example User" is a valid name), assert_not #user.valid? evaluates to false, the test fails, and our valid name does not get written to the database. In pseudocode, this line could translate thusly: assert_not (is the user valid? yes) ==> assert_not(true) ==> false. Similarly, "We do not assert that the user is true", which evaluates to false. Because the test evaluates to false (even with a true/valid user name), this valid name is not written to the database.
If someone could explain how this makes any sense, I'd greatly appreciate it, and detailed answers walking me through it would be even better.
The test passes on an invalid value for
:name, thus allowing a name consisting of blank spaces to be written
to the database...
assert_not returns true (same as test passing) if the expression that follows, #user.valid?, returns false. In effect the test in section 3 says: We're not asserting that the user is valid. Since that expectation/assertion is correct, it returns true and the test passes. Nothing has been saved to the database.
You can add a validation as you add a string " " consist two spaces
class User < ActiveRecord::Base
validates :name, presence: true, allow_blank: false
validate :name_of_valid
def name_of_valid
self.errors.add :base, 'My string can not be empty' if self.name == " "
end
end
and then
test "name should be present" do
#user.name = " "
# => name: " " email: "example#example.com"
assert_not #user.valid?
# => assert_not true
end
I had the same problem. Only with presence: true the test fails and when I add allow_blank: false it passes.
I have been using RSpec and Cucumber for a few months. But since I'm the only developer here, it's been all self learning, so I'm asking this to clarify where to test what.
I'm currently creating a CMS for Coupons. There's a form to create a new coupon. I have the happy path working and tested in Cucumber. Should I also have a test for when the form is filled out incorrectly? If so, should I be creating a scenario for each case that doesn't pass my validations?
I already have my validations tested in RSpec:
it { should validate_presence_of(:name) }
it { should validate_presence_of(:code) }
describe "validations" do
specify "the start date must be before the end date" do
site_wide_coupon = SiteWideCoupon.new(name: "Free Shipping", code: "ABC123")
site_wide_coupon.start_date = 1.month.from_now
site_wide_coupon.end_date = 1.month.ago
expect(site_wide_coupon.valid?).to be_false
expect(site_wide_coupon.errors.full_messages).to include("Start date must be before the end date")
end
context "it is a flat amount coupon" do
let(:site_wide_coupon) {
site_wide_coupon = SiteWideCoupon.new(name: "Flat Amount", code: "ABC123")
site_wide_coupon.start_date = 1.month.ago
site_wide_coupon.end_date = 1.month.from_now
site_wide_coupon.valid?
site_wide_coupon
}
it "validates presence of discount_amount" do
expect(site_wide_coupon.errors.full_messages).to include("Discount amount can't be blank")
end
it "doesn't validate the presence of discount_percentage" do
expect(site_wide_coupon.errors.full_messages).not_to include("Discount percentage can't be blank")
end
end
context "it is a percentage amount coupon" do
let(:site_wide_coupon) {
site_wide_coupon = SiteWideCoupon.new(name: "Percentage Amount", code: "ABC123")
site_wide_coupon.start_date = 1.month.ago
site_wide_coupon.end_date = 1.month.from_now
site_wide_coupon.valid?
site_wide_coupon
}
it "validates presence of discount_amount" do
expect(site_wide_coupon.errors.full_messages).to include("Discount percentage can't be blank")
end
it "doesn't validate the presence of discount_percentage" do
expect(site_wide_coupon.errors.full_messages).not_to include("Discount amount can't be blank")
end
end
end
describe "#flat_amount?" do
context "name equals 'Flat Amount'" do
it "returns true" do
site_wide_coupon = SiteWideCoupon.new(name: "Flat Amount")
expect(site_wide_coupon.flat_amount?).to be_true
end
end
context "name doesn't equal 'Flat Amount'" do
it "returns false" do
site_wide_coupon = SiteWideCoupon.new(name: "Something else")
expect(site_wide_coupon.flat_amount?).to be_false
end
end
end
describe "#percentage_amount?" do
context "name equals 'Percentage Amount'" do
it "returns true" do
site_wide_coupon = SiteWideCoupon.new(name: "Percentage Amount")
expect(site_wide_coupon.flat_amount?).to be_true
end
end
context "name doesn't equal 'Flat Amount'" do
it "returns false" do
site_wide_coupon = SiteWideCoupon.new(name: "Something else")
expect(site_wide_coupon.flat_amount?).to be_false
end
end
end
Would it be necessary to test that my validations fire in Cucumber then? Or would that just be repeating my RSpec tests and not adding any value?
Or maybe I should only have one test that submits the form without filling anything in and testing that the errors show up on the page?
What do you guys usually do?
My two cents: Forget about cucumber. Having to keep another whole DSL in your head and maintain the steps results in a lot of wasted time in the long run (especially if you're working by yourself). RSpec is a much better way to go as it's closer to plain Ruby, and you can probably even transition to MiniTest::Spec fairly easily. Don't erase your cucumber tests all at once, but delete them as they fail and replace them with specs.
As for duplication in tests, I tend to not mind it as much. Since you're defining detailed scenarios and their expected outcomes, a repetitive series of tests can be more explicit and easier to understand than even a well-refactored suite.
It comes down to why you're testing in the first place. The goal of a test suite is to warn developers in the event that something in broken so that they can fix it. Imo double testing is a form of waste since the goal of the test suite has been fulfilled if after writing one test.
So long story short I reckon it's fine to test it just in rSpec unless your cucumber test is testing something different.
I think that having validation tests only in RSpec is not sufficient. If you want to make sure that user can see the error message you have to use Cucumber/Capybara/Jasmine...
I agree that testing all validations in Cucumber is not a good idea.
However testing one validation per form might be really helpful.
My approach is that I test each validation in RSpec + one Cucumber scenario per form with failing one validation.
I recently started learning RoR and TDD, and am having trouble figuring out the best way to handle this scenario.
I have an ActiveRecord model with two fields which share the same validations.
How do I write an RSpec test which utilizes the same tests for the similar fields?
"shared examples" looked like a promising feature to utilize in this scenario, but does not seem to work, as I need to test the entire model but am only passing the individual field to the shared example.
Below is my failed attempt:
describe Trip do
before do
#trip = trip.new(date: '2013-07-01', city_1: "PORTLAND",
city_2: "BOSTON")
end
subject { #trip }
shared_examples "a city" do
describe "when not uppercase" do
before { city = city.downcase }
it { should_not be_valid }
end
end
describe "city_1 must be valid" do
it_should_behave_like "a city" do
let!(:city) { #trip.city_1}
end
end
describe "city_2 must be valid" do
it_behaves_like "a city" do
let!(:city) { #trip.city_2}
end
end
end
This fails because updating the city variable does not update trip model. Is there a way to dynamically tie it back to the model?
BTW, all the tests work on their own if I paste under each field. It just will not work in the context of the shared_example.
Any guidance would be greatly appreciated.
You can perform the assertion within a loop, and use a little metaprogramming, but I would advise against it. Tests should be as simple and straightforward as possible, even if that means having a little duplication. If only two fields are involved, just repeat it.
I'm writing integration tests using Rspec and Capybara. I've noticed that quite often I have to execute the same bits of code when it comes to testing the creation of activerecord options.
For instance:
it "should create a new instance" do
# I create an instance here
end
it "should do something based on a new instance" do
# I create an instance here
# I click into the record and add a sub record, or something else
end
The problem seems to be that ActiveRecord objects aren't persisted across tests, however Capybara by default maintains the same session in a spec (weirdness).
I could mock these records, but since this is an integration test and some of these records are pretty complicated (they have image attachments and whatnot) it's much simpler to use Capybara and fill out the user-facing forms.
I've tried defining a function that creates a new record, but that doesn't feel right for some reason. What's the best practice for this?
There are a couple different ways to go here. First of all, in both cases, you can group your example blocks under either a describe or context block, like this:
describe "your instance" do
it "..." do
# do stuff here
end
it "..." do
# do other stuff here
end
end
Then, within the describe or context block, you can set up state that can be used in all the examples, like this:
describe "your instance" do
# run before each example block under the describe block
before(:each) do
# I create an instance here
end
it "creates a new instance" do
# do stuff here
end
it "do something based on a new instance" do
# do other stuff here
end
end
As an alternative to the before(:each) block, you can also use let helper, which I find a little more readable. You can see more about it here.
The very best practice for your requirements is to use Factory Girl for creating records from a blueprint which define common attributes and database_cleaner to clean database across different tests/specs.
And never keep state (such as created records) across different specs, it will lead to dependent specs. You could spot this kind of dependencies using the --order rand option of rspec. If your specs fails randomly you have this kind of issue.
Given the title (...reusing code in Rspec) I suggest the reading of RSpec custom matchers in the "Ruby on Rails Tutorial".
Michael Hartl suggests two solutions to duplication in specs:
Define helper methods for common operations (e.g. log in a user)
Define custom matchers
Use these stuff help decoupling the tests from the implementation.
In addition to these I suggest (as Fabio said) to use FactoryGirl.
You could check my sample rails project. You could find there: https://github.com/lucassus/locomotive
how to use factory_girl
some examples of custom matchers and macros (in spec/support)
how to use shared_examples
and finally how to use very nice shoulda-macros
I would use a combination of factory_girl and Rspec's let method:
describe User do
let(:user) { create :user } # 'create' is a factory_girl method, that will save a new user in the test database
it "should be able to run" do
user.run.should be_true
end
it "should not be able to walk" do
user.walk.should be_false
end
end
# spec/factories/users.rb
FactoryGirl.define do
factory :user do
email { Faker::Internet.email }
username { Faker::Internet.user_name }
end
end
This allows you to do great stuff like this:
describe User do
let(:user) { create :user, attributes }
let(:attributes) { Hash.new }
it "should be able to run" do
user.run.should be_true
end
it "should not be able to walk" do
user.walk.should be_false
end
context "when user is admin" do
let(:attributes) { { admin: true } }
it "should be able to walk" do
user.walk.should be_true
end
end
end
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.