Rails: 4.1.7
RSpec-rails version: 3.1.0
I am trying to write a request spec to test the create action for my BlogPost model. RSpec doesn't seem to like the data params that I am trying to pass in because I keep seeing the following error when running the test:
ActiveRecord::UnknownAttributeError:
unknown attribute: blog_post
RSpec code:
require 'rails_helper'
RSpec.describe BlogPost do
let!(:admin_user) { Fabricate(:admin_user) }
let!(:blog_post) { Fabricate(:blog_post) }
before { login(admin_user.email, admin_user.password) }
describe 'POST /admin/blog_posts' do
before do
post admin_blog_posts_path, blog_post: {
body: 'body text',
title: 'title text',
cover_image: '/assets/post.png',
summary: 'cool post bruh',
live_demo_url: 'livedemo.com',
live_demo_url_text: 'click here',
github_source: 'github.com/awesome'
}
end
it 'should redirect to the blog posts index page' do
expect(response).to redirect_to(admin_blog_posts_path)
follow_redirect!
end
end
end
There is something about using the word blog_post it doesn't seem to like. Because I tried changing it to an arbitrary word like so and the error went away:
post admin_blog_posts_path, someresource: {
title: 'title text'
}
Also I have a put request spec, which is also using blog_post and that works fine:
describe 'PUT /admin/blog_posts/:id' do
before do
put admin_blog_post_path(blog_post.id), blog_post: {
title: 'My new title'
}
end
...
end
So I'm not really sure why RSpec doesn't like my post admin_blog_posts_path, blog_post ... syntax. Any ideas?
Noob status over here.
In my controller create action I had:
#blog_post = BlogPost.new(permitted_params)
It was raising the error because I passed in the entire params so it read blog_post as an attribute for the BlogPost resource.
The fix: #blog_post = BlogPost.new(permitted_params[:blog_post])
Related
I have 2 models Country and Language linked with HABTM relation.
I'm using Rails API with ActiveModelSerializer and Ember JS as frontend.
So how is it possible to add a new language to country.languages collection ?
On the Ember side I'm trying to add a new language as follows:
#router
actions: {
saveLanguage(language) {
let controller = this.get('controller');
let country = controller.get('aCountry');
country.get('languages').pushObject(language);
country.save();
}
}
This calls CountriesController#update action in Rails.
Here is how I deserialize params hash in Rails controller:
#countries_controller.rb
def country_params
ActiveModelSerializers::Deserialization.jsonapi_parse!(params)
end
And here is what it returns:
{:code=>"BE", :name=>"BELGIUM", :modified_by=>"XXX", :id=>"5", :language_ids=>["374", "231", "69"]}
So I'm getting all I need:
country ID => id=5
languages IDS => both existing ones (2) and a new one.
How to properly update the country ? Thank you.
I figured out hot to add/delete an item to/from a association.
So on Ember side it looks like that:
actions: {
deleteLanguage(language) {
let controller = this.get('controller');
let country = controller.get('aCountry');
country.get('languages').removeObject(language);
country.set('modifiedBy', this.get('currentUser.user').get('username'))
country.save();
},
saveLanguage(language) {
let controller = this.get('controller');
let country = controller.get('aCountry');
country.get('languages').pushObject(language);
country.set('modifiedBy', this.get('currentUser.user').get('username'))
country.save();
}
And on the Rails side everything happens in the CountriesController:
class CountriesController < ApplicationController
...
def update
if #country.update(country_params)
json_response #country
else
json_response #country.errors, :unprocessable_entity
end
end
private
def find_country
#country = Country.includes(:languages).find(params[:id])
end
def country_params
ActiveModelSerializers::Deserialization.jsonapi_parse!(params,
only: [
:id,
:modified_by,
:languages
])
end
end
Sure, I'll have to add some errors handling on Ember side just to display a confirmation of the update or the errors.
Method json_response is just my custom helper defined as follows in concerns for controllers:
module Response
def json_response(object, status = :ok, opts = {})
response = {json: object, status: status}.merge(opts)
render response
end
end
Hope this helps. You can find more about mode relations in Ember Guide.
So there's an argument that I'm reading about that says Controller tests should be unit tests of the controllers and Request specs should be more of an integration test that involves the router, the controller, and the response. That's the philosophy in the codebase that I'm working in. Given that, what's the best way to write both tests given that I have this controller:
class Api::WineController < ApplicationController
def show
wine = Wine.find(params[:id])
render json: { id: wine.id, varietal: wine.varietal }, status: :ok
rescue ActiveRecord::RecordNotFound
render json: { error: { message: "Wine not found") } }, status: :bad_request
end
end
So my request spec is the one that looks like a typical, integration-y controller test that most people write:
require 'rails_helper'
RSpec.describe "Wine API", type: :request do
describe '#get /api/wine' do
let!(:wine) { create(:wine) }
subject { get "/api/wine_invite_beta/wine_tokens/:id", params }
let(:params) {
{
id: wine.id
}
}
context "and the wine exists" do
it "returns back a JSON response of names" do
subject
expect(JSON.parse(response.body)).to eq("id" => wine.id, "varietal" => wine.varietal)
expect(response).to have_http_status(:ok)
end
end
context "and wine does not exist" do
it "returns back a JSON response of errors" do
subject
expect(JSON.parse(response.body)).to eq("error" => { "message"
....
end
end
end
end
So the request spec uses the http methods (:get is the one used), to hit the router which then hits the controller. This seems more like an integration test.
The controller test should do without the router and just test the class and its methods like I would test the model. Is that right? Do people agree? This is how I'm writing it:
require 'rails_helper'
RSpec.describe Api::WinesController, type: :controller do
describe "#show" do
let(:controller_instance) { described_class.new }
subject { controller_instance.show }
before do
allow(controller_instance).to receive(:params) { params }
end
let(:params) { { id: 1} }
context "and wine token exists for user" do
let!(:wine) { create(:wine) }
it "calls render with the wine token data" do
expect(controller_instance).to receive(:render).with(
json: {
id: wine.id,
varietal: wine.varietal
},
status: :ok
)
subject
end
end
Is that okay? I'm merely setting an expectation that a method is called within the controller because that seems like a command. That feels wrong though. The render method seems more like a query than a command and so perhaps I should be inspecting the state of the controller after render is called. Does anyone know how to do this?
More generally, what do people think of this approach to making controller vs request specs?
Currently doing Rails Unit Test, using minitest-rails.
I'm using bootstrap editable js to directly update data in view.
I having trouble asserting value correctly, got Failure result.
Only for the function that I used bootstrap editable, since it uses other way to send parameters than normal Rails update action.
Please help a look at my codes.
In my controller:
def edit_job_type
update_common_table('job_types', params[:pk], params[:name], params[:value])
end
In my included module:
def update_common_table(table, id, key, value)
begin
case table
when 'job_types'
#record = JobType.find(id)
end
case key
when 'en_name'
#record.en_name = params[:value]
edit_field = 'English Name'
end
#record.last_updated_by = session[:username]
#record.save
render json: {
status: 'success',
message: "#{edit_field} was successfully updated.",
updated_at: #record.updated_at.to_time.strftime("%a, %e %b %Y %H:%M"),
updated_by: session[:username]
}
rescue => error
render json: {status: 'error', message: error.message}
end
end
In my minitest-rails, controller:
setup do
#job_type = job_types(:waiter)
end
test "should update job_type" do
patch :edit_job_type, id: #job_type.id, job_type: { pk: #job_type.id, name: 'en_name', value: "janitor" }
assert_response :success, message: 'English Name was successfully updated.'
#job_type.reload
assert_equal "janitor", #job_type.en_name # this one FAILS, not updated value
end
In my fixtures > job_types:
waiter:
en_name: waiter
When I run rake test:
I got failure result, because the update was failed.
Expected: "New Job Type Updated"
Actual: "waiter"
Still getting the default value "waiter", instead of "janitor"
Please help to figure out how can I fixed my test.
SOLVED
Finally I've made a work around after thorough searching.
The solution was to use a XHR method since bootstrap editable uses POST method.
Before:
test "should update job_type" do
patch :edit_job_type, id: #job_type.id, job_type: { pk: #job_type.id, name: 'en_name', value: "janitor" }
assert_response :success, message: ' Successfully updated.'
#job_type.reload
assert_equal "janitor", #job_type.en_name # this one FAILS, not updated value
end
After:
test "should update job_type" do
xhr :post, :edit_job_type, format: :js, pk: #job_type.id, name: 'en_name', value: "janitor"
assert_response :success, ' Successfully updated.'
#job_type.reload
assert_equal "janitor", #job_type.en_name
end
Thanks to this tutorial, Building Rails Test
I have an endpoint that responds with an object's attributes + attributes that are added from an ActiveModelSerializer. I want to write a test that checks to see if the response has keys.
Let's hypothetically say that the object (say a tree) has these keys
expected_tree_attributes = [:height, :age, :color]
How do I write this test properly? Can I write:
subject { post :obtain_tree_info, { id: tree.id } }
response = JSON.parse(subject.body)
expected(response).to include(*expected_tree_attributes)
IS that... acceptable?
Please consider to use rspec-api-matchers gem
or airborne gem
With these you can do:
# api_matchers
response = JSON.parse(subject.body)
expect(response).to be_success
expect(response).to have_json_node(:height).with(tree.height)
expect(response).to have_json_node(:age).with(tree.age)
expect(response).to have_json_node(:color).with(tree.color)
# or
expect(response).to have_json_node(:age).with("123")
Airborne
describe 'sample spec' do
it 'should validate types' do
post '/api/v1/obtain_tree_info', {id: tree.id}
expect_json_types(height: :int, age: :int_or_null, color: :string)
end
end
Can someone help me with Django-ratings app? I'm trying to post a rating but the app doesn't seem to do anything. What part am I missing here? Really hard to find examples out there..
My function looks like this (jquery Raty plugin):
$('.raty').raty({
click: function(score, evt) {
var vote_url = "/rate/" + $(this).attr('value') + "/" + score + "/" ;
$.ajax({
url: vote_url,
type: "GET",
success: function(){
alert('Vote successful!');
}
});
}
});
The GET seems to work but i can see in my admin that no votes/scores are registered.
In my URL:
url(r'rate/(?P<object_id>\d+)/(?P<score>\d+)/', AddRatingFromModel(), {
'app_label': 'myapp',
'model': 'MyModel',
'field_name': 'rating',
}),
EDIT:
I'm getting a 404 error "Invalid model or app_label". But I'm pretty sure thoose are the correct ones.
This applications does not need the POST request. The easiest way to solve the problem is to set 'GET' method in ajax request
$.ajax({
...
type: 'GET'
...
To avoid 404 you need to write model name in lowercase. In django.contrib.contenttypes app_label and model use lowercase.
url(r'rate/(?P<object_id>\d+)/(?P<score>\d+)/', AddRatingFromModel(), {
...
'model': 'mymodel',
...
}),