let(:product) { FactoryGirl.create(:product) }
it "should blah" do
product.name = "a"
product.save!
post :update, id: product.id, product: { name: "x" }
# assuming :update changes the product's name to params[:name]
product.reload.name.should == "x"
end
The should always fails unless I do something like
Product.find(product.id).name.should == "x"
Am I misusing let?
If I work with #product created within before :each and #product.reload it's fine.
If you break during execution of a spec and call self.class.method(:let).source, you get something like:
def let(name, &block)
define_method(name) do
__memoized.fetch(name) {|k| __memoized[k] = instance_eval(&block) }
end
end
Upon further inspection, __memoized is just a simple Hash. I don't think there's anything fancy going on here. What precise versions of rails and rspec were you using?
Related
I am having a hard time figuring out how to test this helper because current_group is not defined in my test case and I am not sure how I can stub it.
module Admin
module EmployeesHelper
def upload_access
policy(current_group).can_bulk_create_employees?
end
def dashboard_params
download_employee_url = upload_access ?
download_admin_group_employees_import_csv_index_path(current_group) : nil
upload_employee_url = upload_access ?
admin_group_employees_import_csv_index_path(current_group) : nil
make_hash(upload_employee_url, download_employee_url)
end
private
def make_hash(upload_url, download_url)
{
employees: #employees,
addEmployeeUrl: new_admin_group_employee_path(current_group),
terminated_employees: #terminated_employees,
new_employees: #new_employees,
test_employees: #test_employees,
group_id: current_group.id,
downloadEmployeeUrl: download_url,
uploadEmployeeUrl: upload_url
}
end
end
end
Here's what my test looks like, but it fails because current_group is not defined.
require 'rails_helper'
describe Admin::EmployeesHelper do
let!(:group) { create(:group) }
before do
# This stub doesn't work because helper doesn't implement current_group
allow(helper).to receive(:current_group).and_return(group)
end
it 'returns correct dashboard props' do
allow(helper).to receive(:upload_access).and_return(true)
props = helper.dashboard_params
expect(props).values_at(:downloadEmployeeUrl, :uploadEmployeeUrl).should_not include(nil)
end
end
If the problem is only with current_group method, you could include this helper in a dummy class. Something like this:
let(:klass) {
Class.new do
include Admin::EmployeesHelper
def current_group
nil
end
end
}
let(:instance) { klass.new }
before do
allow(instance).to receive(:current_group).and_return('whatever') # test-specific group
end
it 'returns correct dashboard props' do
allow(instance).to receive(:upload_access).and_return(true)
props = instance.dashboard_params
expect(props).values_at(:downloadEmployeeUrl, :uploadEmployeeUrl).should_not include(nil)
end
Although I foresee that you'll have to include something for the url helpers too. And set up those instance vars. All in all, it probably isn't worth the trouble.
I ended up refactoring the helper to have current_group as an argument instead.
I am new to Rails and I want to test my set strong parameters of the Book model with a controller test. I am using Minitest and Rails 4.
Book model:
class Book < ActiveRecord::Base
validates :title, presence: true, length: { in: 1..150 }
end
Book controller wit params:
def create
#book = Book.new book_params
if #book.save
redirect_to action: "index", notice: 'Success.'
else
render :new
end
end
private
def book_params
params.require(:book).permit(:title, :cover_image)
end
My idea for a test - does fail, because it creates an entry:
assert_no_difference('Book.count') do
post :create, book: {
id: 123,
title: "Lorem ipsum"
}
end
How can I get the tests go green and is it correct to test the strong parameters with a controller test?
I am looking for an answer to almost the same question. When using Rails 5 I eventually came up with a solution (call it workaround if you like :-) for testing that the unwanted params don't actually get through. In my (simplified here) case I want to disallow some "security critical" params being passed through when creating a new user.
In the controller (permitting only email and password):
private
def user_params
params.require(:user).permit(:email, :password)
end
In the integration test:
test "not permitted signup data submitted" do
new_user_email = "tester_" + (0...10).map { ('0'..'9').to_a[rand(26)] }.join + "#testing.net"
get signup_path
assert_difference 'User.count', 1 do
post signup_path, params: { user: { email: new_user_email, password: "testpassword", role_id: 1 } }
end
user = User.last
assert user.email == new_user_email
assert user.role_id == nil
end
Here I submit an additional, "sensitive" parameter role_id with the value of 1 (admin). I expect the user to be created. Then I read that newly (last) created user and expect it to have role_id empty (nil). To make the test fail I add :role_id to user_params. Removing it, makes the test pass. Obviously if your attribute can't be nil (aka NULL in SQL), you can test for default value being stored instead of the submitted one.
Since Rails drops all unpermitted parameters not in permit, the new record will be created, hence the test will be red.
Although, one can raise an exception with the action_on_unpermitted_parameters method when non-whitlisted parameters are submitted.
I do like to test Strong Parameters in the controller. I also like to test them more directly, so this is how I do it.
First, I have a test helper that is required in my test/test_helper.rb file:
test/test_helpers/controller_strong_params_helper.rb
# frozen_string_literal: true
module ControllerStrongParamsHelper
def assert_requires_param(param, &block)
#controller.params = ActionController::Parameters.new()
assert_raises(ActionController::ParameterMissing) { yield }
#controller.params = ActionController::Parameters.new(stub_parameter: {})
assert_raises(ActionController::ParameterMissing) { yield }
# It's not enough to have an empty required parameter, there needs to be something inside.
#controller.params = ActionController::Parameters.new(param => {})
assert_raises(ActionController::ParameterMissing) { yield }
#controller.params = ActionController::Parameters.new(param => '')
assert_raises(ActionController::ParameterMissing) { yield }
#controller.params = ActionController::Parameters.new(param => {something_inside: 'something'})
assert_nothing_raised { yield }
end
end
This lets me easily test the strong params that are not optional.
Now assume I have these strong params in my ExampleController:
def example_params
params.require(:example).permit(:id,
:name,
:description)
end
private :example_params
This is what my minitest tests would look like:
test/controllers/example_controller_test.rb
###############################################
test '#example_params should require an example parameter' do
assert_requires_param(:example) { #controller.send(:example_params) }
end
###############################################
test '#example_params should permit some expected example parameters' do
# Using hash rockets so the equality check works.
expected_permitted_params = { 'id' => nil,
'name' => nil,
'description' => nil }
# Specifically merge in any potential non-attribute parameters here if present/needed.
all_params = { example: Example.new.attributes }
#controller.params = ActionController::Parameters.new(all_params)
actual_permitted_params = #controller.send(:example_params)
assert_equal(expected_permitted_params, actual_permitted_params)
end
I'm trying to write a rspec test for a mixin class. I have the following.
module one
module two
def method
method_details = super
if method_details.a && method_details.b
something
elsif method_details.b
another thing
else
last thing
end
end
end
end
Now I have mocked the "method" object that will be passed to the class.
But I'm struggling to access the super method.
I did,
let(:dummy_class) { Class.new { include one::two } }
How to pass the mocked method object to this dummy class?
How do I go about testing this? New to ruby, can someone show me a direction with this.
Thanks in advance.
UPDATE:
I tried,
let(:dummy_class) {
Class.new { |d|
include one::two
d.method = method_details
}
}
let (:method_details){
'different attributes'
}
still doesn't work. I get undefined local variable or method method_details for #<Class:0x007fc9a49cee18>
I personally test mixing with the class. Because the mixing (module) itself has no meaning unless its attached to a class/object.
Ex:
module SayName
def say_name
p 'say name'
end
end
class User
include SayName
end
So I believe you should test your module with attached to the relevant class / object.
How ever this is a different perspective on testing mixings
HTH
I think that in your specs, you'll need to explicitly provide a super class definition for when super is called in #method as "you can't mock super and you shouldn't".
I've attempted to spec out all three of your scenarios with the following minor changes:
Changed your example code slightly to become valid Ruby
Changed #method to #the_method so it doesn't conflict with Object#method
Used OpenStruct to represent the object that super returns, because all I know is that it's an object that has methods #a and #b. You can change that out as appropriate for your real specs
Copy and paste the class and specs below into a file and give them a try:
module One
module Two
def the_method
method_details = super
if method_details.a && method_details.b
'something'
elsif method_details.b
'another thing'
else
'last thing'
end
end
end
end
RSpec.describe One::Two do
require 'ostruct'
let(:one_twoable) { Class.new(super_class) { include One::Two }.new }
describe '#the_method' do
let(:the_method) { one_twoable.the_method }
context 'when method_details#a && method_details#b' do
let(:super_class) do
Class.new do
def the_method
OpenStruct.new(a: true, b: true)
end
end
end
it 'is "something"' do
expect(the_method).to eq('something')
end
end
context 'when just method#b' do
let(:super_class) do
Class.new do
def the_method
OpenStruct.new(a: false, b: true)
end
end
end
it 'is "another thing"' do
expect(the_method).to eq('another thing')
end
end
context 'when neither of the above' do
let(:super_class) do
Class.new do
def the_method
OpenStruct.new(a: false, b: false)
end
end
end
it 'is "last thing"' do
expect(the_method).to eq('last thing')
end
end
end
end
I want to search the tracks either by "all of" the filters, or by "any of" the filters. So here is what I got:
tracks_controller.rb
def search
if params[:scope] == "any_of"
Track.search do
any_of do
with(:name, "Thriller")
with(:artist, "Michael Jackson")
end
with(:user_id, current_user.id)
end
elsif params[:scope] == "all_of"
Track.search do
all_of do
with(:name, "Thriller")
with(:artist, "Michael Jackson")
end
with(:user_id, current_user.id)
end
end
It works as expected. But how to refactor the code to make it DRY?
Here it is Sir:
def search
Track.search do
mode = (params[:scope] == 'any_of' ? method(:any_of) : method(:all_of)) # don't use method(params[:scope]) for obvious security reason)
mode.call do
with(:name, "Thriller")
with(:artist, "Michael Jackson")
end
with(:user_id, current_user.id)
end
end
Below I listed some code from simple Rails application. The test listed below fails in last line, because the updated_at field of the post is not changed within the update action of PostController in this test. Why?
This behaviour seems to me a little strange, because standard timestamps are included in Post model, live testing on local server shows that this field is actually updated after returning from update action and first assertion is fulfilled thus it shows the update action went ok.
How can I make fixtures updateable in above meaning?
# app/controllers/post_controller.rb
def update
#post = Post.find(params[:id])
if #post.update_attributes(params[:post])
redirect_to #post # Update went ok!
else
render :action => "edit"
end
end
# test/functional/post_controller_test.rb
test "should update post" do
before = Time.now
put :update, :id => posts(:one).id, :post => { :content => "anothercontent" }
after = Time.now
assert_redirected_to post_path(posts(:one).id) # ok
assert posts(:one).updated_at.between?(before, after), "Not updated!?" # failed
end
# test/fixtures/posts.yml
one:
content: First post
posts(:one)
That means "fetch the fixture named ":one" in posts.yml. That's never going to change during a test, barring some extremely weird and destructive code that has no place in sane tests.
What you want to do is check the object that the controller is assigning.
post = assigns(:post)
assert post.updated_at.between?(before, after)
On a side note if you were using shoulda (http://www.thoughtbot.com/projects/shoulda/) it would look like this:
context "on PUT to :update" do
setup do
#start_time = Time.now
#post = posts(:one)
put :update, :id => #post.id, :post => { :content => "anothercontent" }
end
should_assign_to :post
should "update the time" do
#post.updated_at.between?(#start_time, Time.now)
end
end
Shoulda is awesome.