Is there something to the effect of 'it_doesnt_behave_like' or 'group_will_fail_with(SomeError)'? I don't see anything in the docs, but I feel like this would be desirable functionality. E.g.
shared_examples_for "a foo doer for admin" do
it "does this" do
...
end
it "does that" do
...
end
end
context "when logged in as an admin" do
let(:admin) { FactoryGirl :admin }
before(:each) { sign_in_as admin }
it_behaves_like "a foo doer for admin"
end
context "when logged in as a user" do
let(:user) { FactoryGirl :user }
before(:each) { sign_in_as user }
# is there something like this?
group_will_fail_with("a foo doer for admin", SomeError) / it_behaves_not_like "a foo maker" / etc.
end
If this is an anti-pattern or there is something wrong with my thinking all corrections are appreciated.
Related
I am trying to test my cancancan abilities using rspec
but as opposed to testing for what a particular user can do, I am trying to test for what a user should not be able to do.
Now, I have a block of context like so:
context "for a manager" do
before do
#manager = FactoryGirl.build(:user, :manager)
#ability = Ability.new(#manager)
end
it "should not be able to create Questions" do
expect(#ability).not_to be_able_to(:create, Question.new)
end
it "should not be able to read Questions" do
expect(#ability).not_to be_able_to(:read, Question.new)
end
it "should not be able to update Questions" do
expect(#ability).not_to be_able_to(:update, Question.new)
end
it "should not be able to delete Questions" do
expect(#ability).not_to be_able_to(:destroy, Question.new)
end
end
This clearly shows that a user of type manager should not have any form of access to the Question model.
Is there a direct way to write this whole block in a single it block, with only one expect?
I have thought about writing it as follow:
context "for a manager" do
before do
#manager = FactoryGirl.build(:user, :manager)
#ability = Ability.new(#manager)
end
it "should not be able to manage Questions" do
expect(#ability).not_to be_able_to(:manage, Question.new)
end
end
But I'm thinking that this may not necessarily do what I'm intending it to do, as this test will pass is as much as one of the ability for that resource is not granted.
So, in short, is there a direct way to test such scenarios? Thanks to all.
First of all, I advise you to use an explicit subject for #ability so you can use the one-liner syntax like in the example below.
describe Role do
subject(:ability){ Ability.new(user) }
let(:user){ FactoryGirl.build(:user, roles: [role]) }
context "when is a manager" do
let(:role){ FactoryGirl.build(:manager_role) }
it{ is_expected.not_to be_able_to(:create, Question.new) }
it{ is_expected.not_to be_able_to(:read, Question.new) }
it{ is_expected.not_to be_able_to(:update, Question.new) }
it{ is_expected.not_to be_able_to(:destroy, Question.new) }
end
end
Updated after your comment
But you can also summarize all this 4 expectations to simply
%i[create read update destroy].each do |role|
it{ is_expected.not_to be_able_to(role, Question.new) }
end
I have this spec that I want to translate to MiniTest.
describe User do
subject { build(:user, provider: 'foo') }
# don't validate presence of password when provider is present
it do
should_not validate_presence_of(:password)
end
end
I tried this. I am getting an error of undefined method 'should_not' for UserTest
class UserTest < ActiveSupport::TestCase
def setup
#user = build_stubbed(:user)
end
test "responds to name" do
assert_respond_to #user, :name
end
should validate_presence_of(:password)
test "do not validate presence of password when provider is present" do
build_stubbed(:user, provider: 'foo')
should_not validate_presence_of(:password)
end
end
I want to change the context for one test, where the subject gets a provider attribute, which should disable the presence validator on the password field.
Here's the full error:
UserTest#test_presence_of_password:
NoMethodError: undefined method `should_not' for #<UserTest:0x007feaa82c1c68>
test/models/user_test.rb:45:in `block in <class:UserTest>'
I found that the better way to do this is to revert to good old MiniTest:
test "uniqueness of email with a different provider" do
email_user = create(:user, email: "foo#bar.com")
facebook_user = build_stubbed(:facebook_user, email: "foo#bar.com")
assert facebook_user.valid?, "should be valid with same email if provider is different"
end
Take a look at the minitest-rails-shoulda gem. If you use it I assume the test would look like this:
describe User do
subject { build_stubbed(:user) }
it { must validate_presence_of(:password) }
describe "when a provider is present" do
subject { build_stubbed(:user, provider: 'foo') }
it { wont validate_presence_of(:password) }
end
end
I am working with rails rspec/capybara/declarative_authorization. I have to run the same test with a lot of different users:
describe "Revision in root folder" do
before do
with_user(#guest) do
visit revisions_path
end
end
it { should have_selector('div.alert.alert-error', text: auth_error_text) }
end
...
describe "Revision in root folder" do
before do
with_user(#user1) do
visit revisions_path
end
end
it { should have_selector('div.alert.alert-error', text: auth_error_text) }
end
The only parameter is the user calling with_user. Can I somehow use only one describe block, and iterate through an array of users, to keep my test DRY. It is important, that #guest and #user1 are created in a before(:all) block, so they are not available at the parsing of the spec.
Any help is appreciated.
describe "Revision in root folder" do
users = [#guest, #user1]
users.each do |user|
before do
with_user(user) do
visit revisions_path
end
end
it { should have_selector('div.alert.alert-error', text: auth_error_text) }
end
end
Not that much DRYer, but do you mind nesting your specs? This way you'll be able to account for any different expected behaviour between users and guests.
describe "Revision in root folder" do
context "as a guest" do
before do
with_user(#guest) do
visit revisions_path
end
end
it { should have_selector('div.alert.alert-error', text: auth_error_text) }
end
context "as a user" do
before do
with_user(#user1) do
visit revisions_path
end
end
it { should have_selector('div.alert.alert-error', text: auth_error_text) }
end
end
If you end up with many more duplicate it statements, you could probably refactor them up into a shared example.
I had the same problem and I resolve it in following way:
[:user_type_1, :user_type_2].each do |user_type|
let(:user) { create(user_type) }
before do
with_user(user) do
visit revisions_path
end
end
it { should have_selector('div.alert.alert-error', text: auth_error_text) }
end
Modern version of Rspec allows to duplicate examples without monkey-patching. Please have a look to this gist https://gist.github.com/SamMolokanov/713efc170d4ac36c5d5a16024ce633ea
different users might be provided as a shared_context - user will be available in actual tests:
shared_context "Tested user" do
let(:user) { |example| example.metadata[:user] }
end
During clone, we can do
USERS.each { |user| example.duplicate_with(user: user) }
Here is my rspec file:
require 'spec_helper'
describe Classroom, focus: true do
describe "associations" do
it { should belong_to(:user) }
end
describe "validations" do
it { should validate_presence_of(:user) }
end
describe "instance methods" do
describe "archive!" do
before(:each) do
#classroom = build_stubbed(:classroom)
end
context "when a classroom is active" do
it "should mark classroom as inactive" do
#classroom.archive!
#classroom.active.should_be == false
end
end
end
end
end
Here is my Classroom Factory:
FactoryGirl.define do
factory :classroom do
name "Hello World"
active true
trait :archive do
active false
end
end
end
When the instance method test runs above, I receive the following error: stubbed models are not allowed to access the database
I understand why this is happening (but my lack of test knowledge/being a newb to testing) but can't figure out how to stub out the model so that it doesn't hit the database
Working Rspec Tests:
require 'spec_helper'
describe Classroom, focus: true do
let(:classroom) { build(:classroom) }
describe "associations" do
it { should belong_to(:user) }
end
describe "validations" do
it { should validate_presence_of(:user) }
end
describe "instance methods" do
describe "archive!" do
context "when a classroom is active" do
it "should mark classroom as inactive" do
classroom.archive!
classroom.active == false
end
end
end
end
end
Your archive! method is trying to save the model to the database. And since you created it as a stubbed model, it doesn't know how to do this. You have 2 possible solutions for this:
Change your method to archive, don't save it to the database, and call that method in your spec instead.
Don't use a stubbed model in your test.
Thoughtbot provides a good example of stubbing dependencies here. The subject under test (OrderProcessor) is a bona fide object, while the items passed through it are stubbed for efficiency.
I am using Ruby on Rails 3.0.9 and RSpect 2. I am trying to refactoring some spec file in the following way (in order to test with less code similar User class object attribute values):
describe User do
let(:user1) { Factory(:user, :users_attribute_a => 'invalid_value') }
let(:user2) { Factory(:user, :users_attribute_b => 'invalid_value') }
let(:user3) { Factory(:user, :users_attribute_c => 'invalid_value') }
it "foreach user" do
[ user1, user2, user3 ].each do |user|
subject { user }
it "should be whatever"
user.should_not be_valid
...
end
end
end
end
However, if I run the above test I get the following error:
Failure/Error: it "should be whatever" do
NoMethodError:
undefined method `it' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_2::Nested_2:0x00000106ccee60>
What is the problem? How can I solve that?
UPDATE after the #Emily answer.
If in the above code I use context "foreach user" do ... instead of it "foreach user" do ... I get the following error:
undefined local variable or method `user1' for #<Class:0x00000105310758> (NameError)
The problem is having one spec nested within another. You need to replace it "foreach user" with context "foreach user".
Edited to add: After some investigation, it looks like helpers set with let are only available inside of the it "should ..." block, and not in the surrounding context. I'd recommend is trying to find a different structural solution. What the best solution is will depend on what you're actually trying to test. I'm guessing what you're trying to do is make sure the user is invalid when you remove any of the required attributes. In that case, what I've done is something like this:
describe User do
let(:user_attributes){ Factory.attributes_for(:user) }
# Testing missing values aren't valid
[:name, :email, :phone].each do |required_attribute|
it "should not be valid without #{required_attribute}" do
User.new(user_attributes.except(required_attribute)).should_not be_valid
end
end
# Testing invalid values aren't valid
[[:email, 'not_an_email'], [:phone, 'not a phone']].each do |(attribute, value)|
it "should not be valid with bad value for #{attribute}" do
User.new(user_attributes.update(attribute => value)).should_not be_valid
end
end
end
If you're doing something that requires more complex differences in the instance you're creating, there may not be a clean way to do it with iteration. I don't think DRY is quite as essential in testing as it is in other parts of your code. There's nothing wrong with having three different contexts for the three user types, and a validity test in each context.
describe User do
context "with user1" do
subject{ Factory(:user, :users_attribute_a => 'invalid_value') }
it{ should_not be_valid }
end
context "with user2" do
subject{ Factory(:user, :users_attribute_b => 'invalid_value') }
it{ should_not be_valid }
end
context "with user3" do
subject{ Factory(:user, :users_attribute_c => 'invalid_value') }
it{ should_not be_valid }
end
end
You're mixing and matching all sorts of rspec stuff. Here's your stuff, fixed:
describe User do
let(:user1) { Factory(:user, :users_attribute_a => 'invalid_value') }
let(:user2) { Factory(:user, :users_attribute_b => 'invalid_value') }
let(:user3) { Factory(:user, :users_attribute_c => 'invalid_value') }
it "should not be valid" do
[ user1, user2, user3 ].each do |user|
user.should_not be_valid
end
end
end
I would do it this way:
describe User do
subject{Factory.build(:user)}
it "should not be valid with invalid users_attribute_a" do
subject.users_attribute_a = "invalid_value"
subject.should_not be_valid
end
it "should not be valid with invalid users_attribute_b" do
subject.users_attribute_b = "invalid_value"
subject.should_not be_valid
end
end
If you want to have "context", then cool, but you can't have variables before your context inside of your context.
If you want to have a specification, then have one, but you can't net "it" statements
UPDATE WITH LEAST POSSIBLE CODE
describe User do
it "should not be valid with other attributes" do
{:users_attribute_a => 'invalid_value', :users_attribute_b => 'invalid_value', :users_attribute_c => 'invalid_value'}.each do |key, value|
Factory.build(:user, key => value).should_not be_valid
end
end
end
The problem is that the helpers that are set with "let" do not exist outside of a example context.
What you're trying to do could be achieved as:
it "does something with all users" do
[user1, user2, user3] do |user|
user.valid?.should be_true
end
end
Both contexts are different
Another way it might work (haven't tried it) it's like this:
context "for all users" do
[:user1, :user2, :user3].each do |user|
it "does something" do
send(user).valid?.should be_true
end
end
end
This should work. Note how the context is written, it will make the output of tests clearer. From writing it this way it implies (to me) that you should make a test for each attribute separately, but it's your choice:
describe User do
let!(:users) {
[:users_attribute_a, :users_attribute_b, :users_attribute_c].map do |a|
Factory(:user, => 'invalid_value')
end
}
context "Given a user" do
context "With an invalid value" do
subject { users }
it { subject.all?{|user| should_not be_valid }
end
end
end