How do I write rspec tests defensively, so that in a scenario at least one expectation must be met yet the failure of others is accepted? (without the input changing). AND is easy enough by listing multiple expectations, but how is OR expressed?
As an example, a user has many posts, and user Bob hacks a form so that when he submits his create post form it sends the id of user Dunc. Currently the application ignores the passed Dunc id, and uses Bob's id as Bob is creating the post. So we could test that the newly created Post has Bob's user_id. However, if in future the code is refactored so that it returns an error message instead of assuming Bob's id, that test would wrongly fail. I want to test the intent, not the implementation.
So i need to test that either no post is created, or that if one is created, its for Bob.
This example is so simple it can be solved by testing
expect { run }.not_to change( Post.where(user_id: #other_user.id), :count )
However I'm looking for the general solution, in more complex cases there can be many conditions. How is "OR" achieved in Rspec? (or is it not possible?)
I don't think it is possible.
I do think you are mistaken when you say that you would be testing implementation, instead of intent in your example.
When you write a test, you test whether what comes out matches your expectation.
Creating a user is something completely different than returning error messages.
In my opinion it would be strange to say: when I do this, I expect this, or that, or that, or that to happen.
In my opinion you should write one test, that tests whether a user is created when you send the correct parameters, and another test that deals with what happens when a user tries to send illegal parameters.
Related
I am doing integration testing using Cucumber. In my ApplicationController, I have a method called current_user that provides the current user object. I use this object to add items to a redis database:
$redis.sadd("cart#current_user.id}", [1,5,2])
In my Cucumber steps I test this functionality:
Then /^the redis database should have "(.+)" item ids/ do |count|
expect($redis.smembers("cart#{current_user.id}").count).to eq count.to_i
end
However, it is my understanding that Cucumber does not have access to controller methods, even if they are under ApplicationController, and therefore I cannot user the current_user method the way I would in my controllers.
What I am doing now is since I am testing features, there is only one user in the database so the current_user.id will always be 1, but if I start adding more users this may not work nicely.
Is there a workaround for this?
Your not really using Cucumber as intended here. What you are doing is testing how your application currently works, but really Cukes is best used to specify what your application does and why its important.
Applying more appropriate usage to your current problem leads to the following questions
What is the reason for storing the ids in Redis?
What benefit does the customer get by having these id's stored?
Taking a wild guess you might be saving a basket so that if the user logs out, their basket would still be populated when they come back. Then your scenario would be something like
Scenario: Remember products in basket
Given I am registered
And I am logged in
When I put some products in my basket
And I log out
And I log in again
Then my basket should still have some products in it
Notice how the scenario is all about WHAT you are doing and WHY its important but reveals nothing about HOW this is going to be done. This is a really good way to critique scenarios. Scenarios that contain HOW stuff are going to be harder to write and much harder to maintain. Anyhow enough of that :)
Now you can use standard cucumber stuff like assigned the user to a variable in one step e.g. #i = create_registered_user and then using that user in the other steps e.g. login as: #i
Note that we don't look at the database, only at what the user sees, and we don't reveal anything about HOW this functionality works in the scenario.
If you want to write tests (rather than scenarios) that do reveal how functionality works and do look at databases for results then I'd suggest that rspec would be better suited for this.
do you have a step to login? if so, you can change it a little so you can control which user logs in:
Given "john_doe" logs in to the app
Then you can search by username and do the login in your step. You can do the same on this step:
Then /^the redis database should have "(.+)" item ids/ do |count|
something like
Then /^the redis database should have "(.+)" item ids for user "(.*)"/ do |count, user_name|
user = User.find_by(username: user_name)
expect($redis.smembers("cart#{user.id}").count).to eq count.to_i
end
I have two controllers that create a user, and on creation, I want to send them an email. Because of DRY, I moved the email logic to an on_create callback. However, now, every time I create a user in my rspec tests (e.g. with factorygirl), it will send an email.
Some possible ideas I've had:
I could mock out this behavior, but then I need to mock it all the time...
I could make it a parameter, (e.g. a boolean that's true only if an email should get sent), but then I'd only add this for the test
What would be the cleanest way of doing this?
It is best practice to invoke the sending of emails from the controllers context, not the models. From the framework's point of view, mailers are very similar to controllers in how they are linked with views and control the flow of action.
I would move the callback logic back into each controller. You are correct it is not strictly DRY but in my opinion this type of scenario it is the most readable and explicit approach, worth the trade.
In your controller tests, you can check a mail was delivered:
before(:each) { ActionMailer::Base.deliveries = [] }
it "does a thing" do
subject
expect(ActionMailer::Base.deliveries.count).to eq 1
end
This approach is future proof should you have to make a change to differentiate these two emails and any developer who picks up the code will not go down a blind alley scratching their head while stray emails go out from them being hidden in the model.
Check edit at bottom of page
My boss has a sitemap up- it's basically just every route as a link, with a button to click that says "valid?" or "ignore" which will mark it valid or ignore it on the page.
He asked me to manually go through and click each link, test that page isn't a 500 or 404, and then mark it valid if it isn't.
This seems silly to me, as it is basically just a user facing test for working routes.
I could, in the same time, write out routing specs in Rspec for all those, but I guess he wants some sort of documentation that this is happening on the front end for himself and users.
I was thinking a fun way to work around this boring clicking would be to do it with some programming WHILE writing the specs. Makes him happy, and also adds actual value and test to the app that can be reused.
Is there a way to, in a spec, write something like:
links = page.all('a.routing-links)
link.each do |link|
link.click
if page status != 404 || 500
Link.find(id).update_attribute("verified", true)
end
end
I tried putting that in my spec, but when link.click hits an incorrect route, it stops the test (which makes sense, as that route is broken and this is a test.
What I'd like is to be able to take that error and use it to update the attribute of my model.
Am I going about this completely wrong? Any better ideas or inspiration?
Thanks
Edit
I agree with the poster who said this is better left to a script or rake task.
I'm a bit lost on how to write a script that will go to a page, find every link, record its status_code, and then find and update a model. Any suggestions or tips? Ideally it would be run within in the application, so that I could have access to my models and controllers.
Thanks
Personally I wouldn't actually put this in a spec since you're not actually expecting anything to fail.
Instead I'd create a quick script, or even rake task to run through the links as you described.
That being said, this article: http://agileleague.com/2012/12/rails-3-2-custom-error-pages-the-exceptions_app-and-testing-with-capybara/ details how to bypass the normal fail in these circumstances, namely:
In your config/environments/test.rb
config.consider_all_requests_local = false
config.action_dispatch.show_exceptions = true
Though this would affect all tests, which is quite possibly not what you want.
Also, a minor thing that you'd probably figure out in no time when testing this - you'll either need to revisit the list page after clicking the link, or rather relying on link clicks, you could visit the href instead which would be a bit quicker.
links = page.all('a.routing-links')
link.each do |link|
visit link[:href]
if page.status != 404 || 500
Link.find(id).update_attribute("verified", true)
end
end
I haven't tested that, so not sure if it would work like that, but you should be able to get the idea.
I am going through Hartl's Rails Tutorial. I'm up to the first exercise of 9.6, where he asks me to test that the User admin attribute isn't accessible. The justification is earlier in the book:
After Listing 9.42, Hartl's Rails Tutorial says
If we omitted the attr_accessible list in the User model (or foolishly added :admin to the list), a malicious user could send a PUT request as follows:
put /users/17?admin=1
The corresponding exercise (exercise 9.6.1) in the tutorial says
add a test to verify that the User admin attribute isn’t accessible
I have completed that test with this code in user_spec.rb:
expect do
#user.update_attributes(:admin => true)
end.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
But I used stackoverflow to get that test. This was my original idea (in user_pages_spec.rb):
expect do
put user_path(user) + "?admin=1"
end.to raise_error(ActiveModel::MassAssignmentSecurity::Error) # or some other error
But I couldn't get it to work.
So my questions are:
Is my idea possible? Isn't it better to test directly for what a potential hacker might do from the command line? Isn't that the idea of Capybara, testing user actions?
If it is possible, is there a difference between testing mass assignment and testing the PUT action?
If it isn't possible, why? Is it just not necessary or am I missing something here?
I think I would argue with you that your test is actually better. Some would argue that the given answer is testing Rails functionality which really isn't your job. However, I do think it's frequently good to test things in several different directions.
I was under the impression from back in my school days that it was impossible to send data via the URI except when doing a GET. A quick search of stackoverflow didn't result in any confirmation. However, the wikipedia article seems to imply it:
http://en.wikipedia.org/wiki/POST_%28HTTP%2
I think the correct line of code would be
put user_path(user), {user: {admin: 1}, id: user.id}
I hope that helps.
I'm using Cucumber to test end to end application behavior in my Rails-based web service. I currently have a Scenario outline that looks like the following (making up a hypothetical scenario here of creating a user with another user):
Scenario Outline: Create a user with another user
Given I want to create a user as a user "<user>"
When I create a user with name "<name>"
And the user's age is "<age>"
Then then the response should be "<response>"
Scenarios: Create user with 3 args
| user | name | age | response |
| bob | joe | 25 | <some_xml_response> |
I'm having a bit of difficulty figuring out how I should write the step definitions for this outline. Basically I'm currently concatenating an XML blob (for name+age) and need to do something similar to how rspec uses :post to post to a controller and see a response. My step definitions currently look like:
Given /^I want to create a user with another user "([^"]*)"$/ do |user|
#reqxml << "<user><creator>#{user}</creator>"
end
When /^I create a user with name "([^"]*)"$/ do |name|
#reqxml << "<name>#{name}</name>
end
And /^the user's age is "([^"]*)"$/ do |age|
#reqxml << "<age>#{age}</age>"
end
Then /^then the response should be "([^"]*)"$/ do |response|
# ?? not sure if I can use rspec :post here?
end
What's the best way to improve this Cucumber outline? I have a lot more I want to test. In rspec this is rather straight forward and maybe the right answer is to stick this in RSpec. But I really want to get better use out of Cucumber and have a better "bigger" picture with end to end user story testing such as the one above.
I use capybara/rspec/cucumber and pickle in concert to minimize the actual amount of step definitions I have to write, and generally it gets the job done.
Pickle makes steps such as "Given a model exists with" automatically available, capybara's web steps take care of the browser automation like "goto route" or and also provides css and xpath functions to verify the output with steps such as "should contain" etc....
This way you can test your whole stack, which is kind of the point of cucumber
My personal opinion is that cucumber is a domain specific language and as such, may not always be able to describe what you are trying to do in a concise fashion. (think complex object relationships). If you have non-technical users that need to read your tests then its pretty good, but it doesn't beat plain rspec otherwise.
Your cucumber steps are a bit askew too. "Given" describes the pre-existing conditions, not you goals. "When" should describe the actions you take to create a user, and "Then" should verify that the actions taken effected your goal.
While I have seen this being done, testing APIs with cucumber brings with it a special kind of pain.
If your API is truly restful, writing "controller" specs for the integration should actually be fine. Or, if you really want "proper" full-stack integration testing: Do yourself a favor and use capybara/rspec instead of Cucumber...