Rspec rails api controller index test - ruby-on-rails

I'm trying to test an api controller action with rspec, but since i am quite new to rspec i can't make it work.
I have the following index action: render json: #website.jobs
I have to find all associated jobs to a website by name. Here is my spec:
let(:endpoint) { FactoryGirl.create(:website) }
let(:job) { FactoryGirl.create(:job) }
subject do
get :index, params: { format: :json,
name: endpoint.name }
end
expect(subject.body).to include(:job)
Am i doing something wrong, or am i missing something ?

You cannot check the object directly with the JSON response. You can do something like this:
it "response with JSON body containing expected jobs" do
hash_body = JSON.parse(response.body)
expect(hash_body).first.to match({
id: job.id,
title: job.title
})
end
You need to change the attributes according to your Job model.
A good tutorial for testing JSON using Rspec: Pure RSpec JSON API testing
Hope this helps.

Related

Rspec - Compare two json values

I got a response from render json: { success: 'Success' }, I met a problem when I want to test if the response received the content like this. My unit test is:
let(:success) do
{
success: "Success"
}
end
it 'responds with json containing the success message' do
expect(JSON.parse(response.body)).to eq(success)
end
I got a failure from my test, which is:
expected: {:success=>"Success"}
got: {"success"=>"Success"}
I tried to add double quote to success:'Success' so that it changed to 'success':'Success' but still have the same problem. I have two questions, the first is why there's a colon before the success, and the second is how could I remove it?
JSON.parse will have string-y keys by default.
my_hash = JSON.parse(response.body)
p my_hash.keys.first.class # String
If you want it to symbolize the keys,
my_hash = JSON.parse(response.body, symbolize_names: true)
p my_hash.keys.first.class # Symbol
Note: the option is called symbolize_names and not symbolize_keys.
Remember that a symbol is not a string:
p :success == 'success' # false
I guess you are trying to test API response with JSON format. You could try json_spec gem with many other helpful features https://github.com/collectiveidea/json_spec

undefined method `content type=' for nil:nilclass in send_data

I'm writing Unit test rspec for my controller
Here is my code:
report_base_controller.rb
def send_data_to_report
params[:user_role_code] = #auth_logic.current_role.code
report_url = #report_logic.report_url(params)
res = #request_logic.get(report_url)
check_request_code(res.code.to_i, params)
filename = params[:call_docurain_flag].to_i.zero? ? '' : "buildee_v2.#{Constants::REPORT_OUT_TYPE[params[:file_code].to_i]}"
if params[:file_code].to_i == 2
send_data(res.body, filename: filename, status: res.code, disposition: 'inline')
else
send_data(res.body, filename: filename, status: res.code)
end
end
report_base_controller_spec.rb
describe "send_data_to_report 【帳票】出面及支払内訳書(請負)" do
let(:report_setting) { create(:report_setting, report_code: '22',
unit_code: 1, report_name: '【帳票】出面及支払内訳書(請負)') }
let(:dummy_auth_logic) { double('dummy_auth_logic') }
before do
controller.instance_variable_set(:#auth_logic, dummy_auth_logic)
controller.instance_variable_set(:#request_logic, dummy_request_logic)
allow(dummy_auth_logic).to receive_message_chain(:current_role, :code).and_return(Roles::USER_ROLES[:PRIME])
allow(dummy_report_logic).to receive(:report_url).and_return('')
allow(dummy_http_response).to receive(:code).and_return(200)
allow(dummy_http_response).to receive(:body).and_return('abc')
allow(dummy_request_logic).to receive(:get).and_return(dummy_http_response)
end
it "Case 1" do
expect(controller.send(:send_data_to_report)).to eq(true)
end
end
But when I run the test I received the message
Failure/Error: send_data(res.body, filename: filename, status: res.code)
NoMethodError:
# undefined method `content_type=' for nil:NilClass
# ./app/controllers/api/private/reports/report_base_controller.rb:35:in `send_data_to_report'
I cannot understand that content_type belongs to which object, so I can't resolve the problem. Please help me to fix this problem. Thanks
content_type is a method from ActionController::Request, and more info can be found here:
https://guides.rubyonrails.org/action_controller_overview.html
I'm not entirely sure how you can fix the error, but the problem is you are trying to mock a very complex rails object, and you are missing certain base variables it requires (content_type, in this case).
In my experience I generally do an integration test on controllers, specially because RSPEC will handle all the request logic for me. If you choose to keep the unit test route you will have to deeply understand how ActionController works, in order to mock the request variables.
Good luck!

How to create Post request to Rails API using Postman?

I am new to Postman. I have a Rails server running on the background. I am trying to mock a POST request, but it is not being accepted.
Let's say the model is called manufacturer_organization.rb. Inside, it requires 3 parameters: organization_id (uuid data type), manufacturer_id (integer data type), and account_number (string data type). manufacturer_organization belongs_to organization and it also belongs_to :manufacturer (vice versa; manufacturer and organization has_many manufacturer_organization)
Inside manufacturer_organizations_controller.rb, I have a create method:
def create
#manufacturer_organization = ManufacturerOrganization.new(manufacturer_organization_params)
if #manufacturer_organization.save
puts "success!"
render json: #manufacturer_organization
else
puts "Sorry, something went wrong"
end
end
I can confirm that I have sufficient authorization; when I perform a GET request I got the right JSON response. I am using rails serializer and I have setup serializer for this model as well. Route is also setup using resources :manufacturer_organizations. My gut feeling says the way I am using postman is wrong.
Here is the screenshot of Postman app. I have the right address on address bar, and I am performing a POST request. I have the three params under key-value.
After I Send it, under my Rails Server log I see:
Started POST "/manufacturer_organizations" for 127.0.0.1 at 2017-04-13 16:56:44 -0700
Processing by ManufacturerOrganizationsController#create as */*
Parameters: {"organization_id"=>"fb20ddc9-a3ee-47c3-bdd2-f710541-ff89c", "manufacturer_id"=>"1", "account_number"=>"A rand
om account number test"}
...
  (0.4ms)  BEGIN
   (0.3ms)  ROLLBACK
Sorry, something went wrong
I can do ManufacturerOrganization.new(organization_id: Organization.last.id, manufacturer_id: Manufacturer.last.id, and account_number: "random test account number") just fine inside rails console.
How can I submit a POST request from postman to add a new manufacturer_organization?
Edit:
def manufacturer_organization_params
api_params.permit(:organization_id, :manufacturer_id, :account_number)
end
whereas inside application_controller.rb
def api_params
#api_params ||= ActionController::Parameters.new(ActiveModelSerializers::Deserialization.jsonapi_parse(params))
end
Edit2:
I added error.full_messages and this is what I got:
Manufacturer can't be blank
Organization can't be blank
Account number can't be blank
Why are they blank?
You can pass the data using params or within the body request.
The best way to do this is using the body, because you can send files and the request becomes more clean without the params.
To send data in the body, you must pass the model name and attribute in the "key" field, and the value in the "value" field, like this:
I don't understand what you do to your params. There is a reason the ActiveModelSerializers::Deserialization is namespaced in the "Model" namespace. It shouldn't be used to serialize or de-serialize internet params, but instead it's for serializing/de-serializing model instances.
If parameters arrive in the correct format ActionController::Base from which AplicationController and thus ManufacturerOrganizationsController inherit will de-serialize them for you. The Rails query parameter format looks as follows:
name=something #=> params[:name] = 'something'
names[]=something1&names[]=something2 #=> params[:names] = ['something1', 'something2']
instance[id]=1&instance[name]=foo #=> params[:instance] = {id: '1', name: 'foo'}
This can also be stacked and is used for nested resources by Rails. Example:
instance[title]=some&instance[nested][name]=thing&instance[nested][ids][]=1&instance[nested][ids][]=2
#=> params[:instance] = {title: 'some', nested: {name: 'thing', ids: ['1', '2']}}
Having said that let's get to your example. First of al let us throw away those manual building of params and stick to the convention:
class ManufacturerOrganizationsController
# ...
private
def manufacturer_organization_params
# arriving params should look like this:
#
#=> params = {
# manufacturer_organization: {
# organization_id: 'fb20ddc9-a3ee-47c3-bdd2-f710541-ff89c',
# organization_id: '1',
# account_number: 'A random account number test'
# }
# }
#
# The method #require raises an exception if the provided key
# is not present or has a blank value (with exception of false).
# If the key is found and has a value present than that value is
# returned.
#
params.require(:manufacturer_organization)
.permit(:organization_id, :manufacturer_id, :account_number)
end
end
With that out of the way let's send the correct formatted params:
+--------------------------------------------+---------------------------------------+
| Key | Value |
|--------------------------------------------|---------------------------------------|
| manufacturer_organization[organization_id] | fb20ddc9-a3ee-47c3-bdd2-f710541-ff89c |
| manufacturer_organization[manufacturer_id] | 1 |
| manufacturer_organization[account_number] | A random account number test |
+--------------------------------------------+---------------------------------------+
Those 2 things combined should let you create your resource successfully.
The key thing you should take from this is that params is not a string containing al the params that should be de-serialized. It already should be de-serialized, if it's not than you might have send your parameters wrong.
Ruby on Rails and Postman - Post request.
Hello, this is an example that I developed with Postman and Rails API.
Postman.
I can't add images but this what you have to add in postman Key = Value
Change to Post Request and send.
book[name] = 'Harry Potter'
book[author] = J.K. Rowling
Ruby on Rails 7.
Rails maintains the same code.
def create
#book = Book.new(book_params)
if #book.save
render json: #book, status: :created, location: api_v1_books_url(#book)
else
render json: #book.errors, status: :unprocessable_entity
end
end
def book_params
debugger
params.require(:book).permit(:name, :author, :price)
end
I hope this helps.

Rspec testing inside a loop

I am trying to test the code inside a loop, how would I go about this:
class MyClass
def initialize(topics, env, config, limit)
#client = Twitter::Streaming::Client.new(config)
#topics = topics
#env = env
#limit = limit
end
def start
#client.filter(track: #topics.join(",")) do |object|
# how would I test the code inside here, basically logical stuff
next if !object.is_a?(Twitter::Tweet)
txt = get_txt(object.text)
end
end
Is there a way to do this?
If think that you can use a double of your Twitter::Streaming::Client that has a method filter and when this method is invoked it returns the desired output:
let(:client) { double 'Twitter Client', filter: twitters }
You will need to built manually the twitters object (sorry by my lack of context but I never used the Twitter client) and then you can make the assertions for the result of the start method.
As you can see, testing that code is quite tricky. This is because of the dependency on the Twitter client gem.
You can go down couple of paths:
Don't test it - the Twitter client gem should provide you with Twitter::Tweet objects. You only test your logic, i.e. get_txt method
Do what #Marcus Gomes said - create a collection double that has the filter method implemented.
What I would prefer to do is to stub the #client.filter call in the spec.
For example, in your spec:
some_collection_of_tweets = [
double(Twitter::Tweet, text: "I'll be back!"),
double(Twitter::Tweet, text: "I dare ya, I double dare ya!")
]
#my_class = MyClass.new(topics, env, config, limit)
allow(#my_class.client).to receive(:filter).and_return(some_collection_of_tweets)
This means that the some_collection_of_tweets collection will be returned every time the class calls #client.filter, and by having the data built by you, you what expectations to set.
One thing that you will have to change is to set an attr_reader :client on the class. The only side effect of this type of testing is that you are tying your code to the interfaces of the Twitter client.
But like everything else... tradeoffs :)
Hope that helps!
Perhaps you could do something like this if you really wanted to test your infinite loop logic?
RSpec.describe MyClass do
subject { MyClass.new(['foo','bar'], 'test', 'config', 1) }
let(:streaming_client) { Twitter::Streaming::Client.new }
describe '#start' do
let(:valid_tweet) { Twitter::Tweet.new(id: 1) }
before do
allow(Twitter::Streaming::Client).to receive(:new)
.with('config').and_return(streaming_client)
end
after { subject.start }
it '#get_txt receives valid tweets only' do
allow(valid_tweet).to receive(:text)
.and_return('Valid Tweet')
allow(streaming_client).to receive(:filter)
.with(track: 'foo,bar')
.and_yield(valid_tweet)
expect(subject).to receive(:get_txt)
.with('Valid Tweet')
end
it '#get_txt does not receive invalid tweets' do
allow(streaming_client).to receive(:filter)
.with(track: 'foo,bar')
.and_yield('Invalid Tweet')
expect(subject).not_to receive(:get_txt)
end
end
end

What does/can fake: do?

I've inherited this code and I'm aware that this is creating a stub for fake api calls. I don't understand how it is working. Can it return only JSON? Can I set a response to simply a 200 success? Is there any documentation on this?
class GuessTransaction < ActiveRestClient::Base
request_body_type :json
get :all, '/transaction', fake: [{last_name:"Durdan", first_name:"Tyler"}]
get :find, '/transaction/:id', fake: {id: "1", first_name:"Tyler", last_name: "Durdan"}
post :create, '/transaction', fake:->(request) { {id: 12345 } }
end
If you read the documentation for active-rest-client
you can find this:
Faking Calls
There are times when an API hasn't been developed yet, so you want to
fake the API call response. To do this, you can simply pass a fake
option when mapping the call containing the response.
class Person < ActiveRestClient::Base
get :all, '/people', fake: [{first_name:"Johnny"}, {first_name:"Bob"}]
end
You may want to run a proc when faking data (to put information from
the parameters in to the response or return different responses
depending on the parameters). To do this just pass a proc to :fake:
class Person < ActiveRestClient::Base
get :all, '/people', fake: ->(request) { {result: request.get_params[:id]} }
end
Based on the Source for active-rest-client.
Excerpt
return handle_response(
OpenStruct.new(
status:200,
body:fake,
response_headers:{
"X-ARC-Faked-Response" => "true",
"Content-Type" => content_type
}
)
)
It appears it will always respond with 200 so you can just do something like fake:{}
This will respond with 200 and an empty body for the response.
Even fake: true should work.

Resources