Is It Possible for Rspec to Expect Change in Two Tables

Is it possible for RSpec to expect change in two tables?

This should be two tests. RSpec best practices call for one assertion per test.

describe "#bar" do
subject { lambda { Foo.bar } }

it { should change { Counter.count }.by 1 }
it { should change { AnotherCounter.count }.by 1 }
end

RSpec: Expect to change multiple

In RSpec 3 you can setup multiple conditions at once (so the single expectation rule is not broken). It would look sth like:

expect {
click_button 'Save'
@user.reload
}.to change { @user.name }.from('donald').to('gustav')
.and change { @user.updated_at }.by(4)
.and change { @user.great_field }.by_at_least(23}
.and change { @user.encrypted_password }

It is not a complete solution though - as far as my research went there is no easy way to do and_not yet. I am also unsure about your last check (if it doesn't matter, why test it?). Naturally you should be able to wrap it within your custom matcher.

RSpec, expect to change with multiple values

I would propose DRYing it up by redefining the test subject (and using stabby lambdas for fun):

describe "recipe creation" do
subject { -> { click_button submit } }
it { should change(Recipe, :count).by(1) }
it { should change(Ingredient, :count).by(1) }
end

Update: Although it may look less DRY, these days I would probably still keep on using the expect syntax, since it's recommended and I'm generally moving away from should, but perhaps make some minor changes for spec readability:

describe "recipe creation" do
let(:creating_a_recipe) { -> { click_button submit } }

it "changes the Recipe count" do
expect(creating_a_recipe).to change(Recipe, :count).by(1)
end

it "changes the Ingredient count" do
expect(creating_a_recipe).to change(Ingredient, :count).by(1)
end
end

Note: you may see in the RSpec documentation for the change matcher that expect uses curly brackets. This is, of course, correct, but the reason that standard parenthesis work in this example is that the code that changes mutable state (contained in creating_a_recipe) is in a lambda that gets called when passed into expect as a parameter.

Regardless, in this case either expect(creating_a_recipe) or expect { creating_a_recipe } can be used successfully, and whichever one you use is up to personal preference.

Rspec's expect change count not working

This doesn't work because you're not waiting long enough for the message addition to actually occur. send_keys returns as soon as the browser has been sent the key event, but knows nothing at all about any request/action triggered by that key press in the browser. This is why direct DB access tests are generally a bad idea in feature/system tests (which should generally just test user visible changes/interactions) and make more sense as request or controller.

That being said you could fix this by just sleeping after sending the key, but a better solution is to use one of the Capybara provided matchers (have waiting/retrying behavior) to synchronize the test.

scenario 'adds to their messages', js: true do
expect do
find('#message_content').send_keys(:enter) }
expect(page).to have_css(...) # check for whatever visible change on the page indicates the action triggered by send_keys has completed
end.to change { current_user.reload.messages.count }.by(1)
end

Note: This test is also very simple for a feature test. It's okay to have multiple expectations in a feature test since it's really meant to test a whole user interaction with a specific feature of your app. You might want to look at combining this test with other tests of the same part of your app.

RSpec test to expect only certain properties of an ActiveRecord model to change

Try something like this

expect { model.method_that_changes_attributes }
.to change(model, :attribute_one).from(nil).to(1)
.and change(model, :attribute_two)

If the changes are not attributes, but relations you might need to reload the model:

# Assuming that model has_one :foo
expect { model.method_that_changes_relation }
.to change { model.reload.foo.id }.from(1).to(5)

EDIT:

After some clarification from the OP comment:

You can do this then

# Assuming, that :foo and :bar can be changed, and rest can not

(described_class.attribute_names - %w[foo bar]).each |attribute|
specify "does not change #{attribute}" do
expect { model.method_that_changes_attributes }
.not_to change(model, attribute.to_sym)
end
end
end

This is essentially what you need.

This solution has one issue though: it will call method_that_changes_attributes for each attribute, and this can be inefficient. If that's the case - you may want to make your own matcher that accepts an array of methods. Start here

How do I expect before block to change something in rspec?

Would you consider changing both your tests to expect syntax to get them under the same context? Perhaps something like:

let(:cat) { create :blue_russian_cat } 

context "empty bowl" do
let!(:bowl) { create(:big_bowl, amount: 0) }
let(:meowing) { -> { meow } } # not sure what meow is, so may not need lambda

it "will annoy the cat" do
expect(meowing).to change(cat.status).from(:placid).to(:annoyed)
end

# here I want to expect that number of PetFishes is going down after `meow`
it "will eat some pet fishes" do
expect(meowing).to change(PetFish, :count).by(-1)
end
end

Rails 5 - Rspec Expect Change Count Not Working

Ok, so I figured out why it wasn't saving a new Post record. For whatever reason, the author that was supposed to be assigned to the post in the :post factory was not being created. This meant that a validation failed.

This is the :post factory I was using:

FactoryGirl.define do
factory :post do
title { Faker::Lorem.sentence }
excerpt { Faker::Lorem.paragraph }
content { Faker::Lorem.paragraph }
published false
published_at nil
post_type "article"
keywords "blog, post, key, words"
author
end
end

And this was my :author factory for my Devise Author model:

FactoryGirl.define do
factory :author do
email { Faker::Internet.email }
password "password"
password_confirmation "password"
end
end

When I was generating the attributes using the FactoryGirl

attributes_for(:post)

method, the author was not being created and therefore the author_id attribute of the post was not being set.

To fix this I had to create the author in the test then set the author_id attribute in the attributes_for method.

So my working test now looks like this:

it "saves the new Post in the database" do
author = create(:author)
expect{
process :create, method: :post, params: { post: attributes_for(:post, author_id: author.id) }
}.to change{Post.count}.by(1)
end

Although this solves my original problem, I'm not sure why the Author was not being created and associated to the Post within the attributes_for call.



Related Topics



Leave a reply



Submit