Ruby Hash Alignment - ruby

I'm trying to correct a linter error in my requests test file. I have a context block as follows:
context 'when the request is valid' do
before(:each) do
post '/api/v1/budgets',headers: authenticated_header(#user), params: {
budget: valid_attributes
}
end
end
However, I keep getting the following error:
C: Layout/ArgumentAlignment: Align the arguments of a method call if they span more than one line.
What is the proper way to align this block?

When looking at the RuboCop Layout/ArgumentAlignment documentation my guess would be:
context 'when the request is valid' do
before(:each) do
post '/api/v1/budgets',
headers: authenticated_header(#user),
params: { budget: valid_attributes }
end
end
When you need multi-line params it should probably look like this:
context 'when the request is valid' do
before(:each) do
post '/api/v1/budgets',
headers: authenticated_header(#user),
params: {
budget: valid_attributes
}
end
end
If you have more than one argument on the first line:
context 'when the request is valid' do
before(:each) do
post '/api/v1/budgets', headers: authenticated_header(#user), params: {
budget: valid_attributes
}
end
end
When you have a long method name you could also consider moving the first argument to the next line to reduce intention for all other arguments:
context 'when the request is valid' do
before(:each) do
a_somewhat_long_method_name
'/api/v1/budgets',
headers: authenticated_header(#user),
params: { budget: valid_attributes }
end
end
The above amuses you use the default configuration (:with_first_argument).

Related

Stubbing out an instance of a class to test a callback given as an attribute

I'm attempting to test a line of code in a proc see (proc { |message| flash[:notice] << message } in the code snippet below) using Rspec 3.9 but I can't seem to stub out the instance to do what I want.
Given the following controller and test, how can I stub out CreateAccount and run the on_success attribute given in the controller?
Here is the controller file
class AccountsController < ApplicationController
def create
CreateAccount.new(
on_success: proc { |message| flash[:notice] << message }
).process
redirect_to action: :index
end
end
Here is the Rspec test file
describe AccountsController, type: :controller do
describe 'POST #create' do
subject(:create_action) { post :create, id: 1 }
let(:success_message) { 'Success!' }
context 'when created account successfully' do
it { is_expected.to redirect_to action: :index }
it do
create_action
expect(flash[:notice]).to include success_message
end
end
end
end
The reason I want to do this is to separate concerns from the controller to the CreateAccount object. It shouldn't matter, but here is the CreateAccount object so far.
class CreateAccount
def initialize on_success: proc { |_| }
#on_success = on_success
end
def call
# Do some meaningful work
success_message = 'Meaningful message'
#on_success.call(success_message)
end
end
I've managed to find 1 solution that works but I would like to know if there's a cleaner way
Note: The controller and CreateAction classes remain the same.
describe AccountsController, type: :controller do
describe 'POST #create' do
subject(:create_action) { post :create, id: 1 }
context 'when created account successfully' do
let(:dummy_class) do
Class.new(CreateAction) do
def call
#on_success.call(self.class.message)
end
def self.message
'Success!'
end
end
end
before { stub_constant 'CreateAction', dummy_class }
it { is_expected.to redirect_to action: :index }
it do
create_action
expect(flash[:notice]).to include dummy_class.message
end
end
end
end

Upload image with RSpec has unexpected class on controller

I'm in trouble with RSpec and fixture_file_upload on post request.
Here's the problem: I'm trying to send an image by upload to create a category with image, but when the image arrives, it changes it's class type.
I'm waiting to get in params[:category][:image] an ActionDispatch::Http::UploadedFile but what I receive is ActionController::Parameters.
My request example:
context 'when creates a new main category with valid params' do
let(:category) do
{ category: { name: 'Bolos E bolos',
description: 'São bolos sim',
locale: 'pt-BR',
image: fixture_file_upload('images/test-image.png', 'image/png') } }
end
post '/v4/categories' do
it 'upload image' do
expect { do_request(category) }.to change { ActiveStorage::Blob.count }.from(0).to(1)
end
end
end
what I got:
Failure/Error: expect { do_request(category) }.to change { ActiveStorage::Blob.count }.by(1)
expected `ActiveStorage::Blob.count` to have changed by 1, but was changed by 0
How do I send the image as upload and get it on the controller as ActionDispatch::Http::UploadedFile instead ActionController::Parameters
I could not get your controller spec working, but I managed to get an equivalent request spec working. Having spent 45+ minutes getting no where, I think this is the best I can do. This seems to work. (Just make sure you have an avatar.jpg in the public folder of your rails app.)
## spec/requests/user_spec.rb
require 'rails_helper'
RSpec.describe "Users", type: :request do
describe "it attaches uploaded files" do
it 'attaches the uploaded file' do
file = fixture_file_upload(Rails.root.join('public', 'avatar.jpg'), 'image/jpg')
expect {
post api_users_path, params: { user: {username: "Ben", avatar: file } }
}.to change(ActiveStorage::Attachment, :count).by(1)
end
end
end

Set http status code 201 to an API response in action controller rails

I'm new to rails and I'm building a Rails app that will function as an API. Currently I don't have any models or database just an Api::ProductController controller:
class Api::ProductController < ApplicationController
def create
Rails.logger.info "product was created and the parameters are #{product_params[:name]}, #{product_params[:age]}"
end
private
def product_params
params.permit(:name, :age)
end
end
. As I continue and wrote the request Rspec:
RSpec.describe Api::productsController, type: :request do
it "creates a product" do
post "/api/products", params: { name: "name", age: "22"}
expect(response).to have_http_status(:created)
expect(response.body).to include("product was successfully created.")
end
end
But when I run the request rspec test on the command line I get the following error:
Failure/Error: expect(response).to have_http_status(:created)
expected the response to have status code :created (201) but it was :no_content (204)
My question is how can I set the status code to :created (201)? is the Head method a good approach? any solution or guiding would be appreciated!
It is common to return json presentation of created item.
Now there is no returning data from your action.
Try to use render json: product_params.to_json, status: :created
def create
Rails.logger.info "product was created and the parameters are #{product_params[:name]}, #{product_params[:age]}"
render status: :created
end

How to share RSpec let variables between shared contexts?

I'm trying to DRY my RSpec request specs by using shared contexts. I'd like to share let variables between shared contexts so that they inherit and extend from one another.
Rspec.shared_context 'JSON request' do
let(:headers) do
{
'Accept' => 'application/json'
}
end
end
Rspec.shared_context 'Authenticated request' do
let(:headers) do
super().merge('Authorization' => "Bearer #{token}")
end
end
Rspec.describe 'user management' do
let(:token) { create(:oauth_token) }
include_context 'JSON request'
include_context 'Authenticated request'
it 'responds with a 200 ok' do
get '/user', headers: headers
expect(response).to have_http_status(:ok)
end
end
Declaring token works as expected, but using super() to override headers returns a NoMethodError suggesting super() is nil.
I'm not aware of a way to refer to the currently defined value of a let variable in a let block. (When I try it I get "stack level too deep".) I'd do what you're trying to do this way:
Rspec.shared_context 'JSON request' do
let(:common_headers) do
{
'Accept' => 'application/json'
}
end
let(:headers) { common_headers }
end
Rspec.shared_context 'Authenticated request' do
let(:headers) do
common_headers.merge('Authorization' => "Bearer #{token}")
end
end
You can use super() to get the value of a predefined let:
RSpec.shared_context 'common' do
let(:foo) { 'foo' }
end
RSpec.describe 'test' do
include_context 'common'
context 'reversed' do
let(:foo) { super().reverse }
it 'works' do
expect(foo).to eq('oof')
end
end
end

DRY rspec syntax for controller specs

Typically in my controller specs I'll do something like this:
describe MyController do
describe 'POST #create' do
let!(:my_model) { initialize_something_here }
before :each do
post :create, my_model: my_model
end
it 'should be successful' do
response.should be_successful
end
... more tests ...
end
end
My question is, when I do assertions that use an expect block such as checking that the count of items in the database is incremented after a create, I have to remove the post call from the before :each block, like this and repeat it for each it statement:
describe MyController do
describe 'POST #create' do
let!(:my_model) { initialize_something_here }
it 'should insert into database' do
expect { post :create, my_model: my_model }.to change(MyModel, :count).by(1)
end
it 'should be successful' do
post :create, my_model: my_model
response.should be_successful
end
... more tests ...
end
end
Is there a DRY-er way to do the post call?
I believe you can use a lambda for that maybe in a let
let(:post_create_model) { -> { post :create, my_model }}
it 'should insert into database' do
expect(post_create_model).to change(MyModel, :count).by(1)
end
it 'should be successful' do
post_create_model.call
response.should be_successful
end

Resources