Continuing from the previous index, we will implement show testing and controller. show is the behavior of finding and returning based on the id of the parameter.
Is the test about returning a response normally and returning 404 when the ID does not exist?
spec/requests/v1/posts_controller.rb
...
+ describe "GET /v1/posts#show" do
+ let(:post) do
+ create(:post, subject: "show test")
+ end
+ it "Normal response code is returned" do
+ get v1_post_url({ id: post.id })
+ expect(response.status).to eq 200
+ end
+ it "subject is returned correctly" do
+ get v1_post_url({ id: post.id })
+ json = JSON.parse(response.body)
+ expect(json["post"]["subject"]).to eq("show test")
+ end
+ it "404 response is returned when id does not exist" do
+ get v1_post_url({ id: post.id + 1 })
+ expect(response.status).to eq 404
+ end
+ end
...
The explanation was missing, but let is lazily evaluated when called. In other words, it will not be executed until the variable post is called, but will be executed when it is called.
In the above example, it will be created when it is called post.id in the it block. Of course, the controller is not implemented, so the test is moss.
app/controllers/v1/posts_controller.rb
...
def show
- # TODO
+ render json: { post: @post }
end
...
Two of the three added tests will pass, but the remaining one will not pass the 404 error test. I'm getting a RecordNotFound exception in Rails and I'm getting a non-json response.
Modify application_controller.rb, which is a super class for all controllers.
app/controller/application_controller.rb
# frozen_string_literal: true
#
#super class of controller
#
class ApplicationController < ActionController::API
+ rescue_from ActiveRecord::RecordNotFound, with: :render_404
+ def render_404
+ render status: 404, json: { message: "record not found." }
+ end
end
By responding as above, when a record not found (ActiveRecord :: RecordNotFound) exception is thrown, it will be rescued and render_404
will be executed.
And as status: 404
, json is returned with a 404 response code.
If implemented so far, the test will pass. Just in case, let's check it with curl.
$ curl localhost:8080/v1/posts/0
{"message":"record not found."}
$ curl localhost:8080/v1/posts/1
{"post":{"id":1,"subject":"hoge","body":"fuga","created_at":"2020-09-05T13:50:01.797Z","updated_at":"2020-09-05T13:50:01.797Z"}}
Since create creates a new record, make sure that it increases by one record, that the added record matches the time of registration, and that it cannot be created when the parameter is incorrect.
spec/requests/v1/posts_controller.rb
+ describe "POST /v1/posts#create" do
+ let(:new_post) do
+ attributes_for(:post, subject: "create_subject test", body: "create_body test")
+ end
+ it "Normal response code is returned" do
+ post v1_posts_url, params: new_post
+ expect(response.status).to eq 200
+ end
+ it "One more case will be returned" do
+ expect do
+ post v1_posts_url, params: new_post
+ end.to change { Post.count }.by(1)
+ end
+ it "subject,body returns correctly" do
+ post v1_posts_url, params: new_post
+ json = JSON.parse(response.body)
+ expect(json["post"]["subject"]).to eq("create_subject test")
+ expect(json["post"]["body"]).to eq("create_body test")
+ end
+ it "Errors are returned when the parameter is invalid" do
+ post v1_posts_url, params: {}
+ json = JSON.parse(response.body)
+ expect(json.key?("errors")).to be true
+ end
+ end
If you use ʻattributes_for`, the object will be returned without saving to the DB like build. At that time, it is returned as a simple object instead of ActiveRecord format, and it is convenient because there are no unnecessary columns at the time of post such as id, created_at, updated_at.
At this time, please be careful not to change the variable name to post
.
At the time of writing the article, I made it post
, and it became post v1_posts_url, params: post
, and the post of get and post was overridden and the test did not work properly ... It took a long time to resolve: sweat_smile:
expect do post v1_posts_url, params: new_post end.to change { Post.count }.by(1)
This block is a test in which Post.count is incremented by 1 by posting. The point is that expect is a block instead of ʻexpect.to eq`.
Implement controller.
app/controllers/v1/posts_controller.rb
...
def create
- # TODO
+ post = Post.new(post_params)
+ if post.save
+ render json: { post: post }
+ else
+ render json: { errors: post.errors }
+ end
end
...
$ curl localhost:8080/v1/posts -X POST -H 'Content-Type: application/json' -d '{"subject":"moge","body":"hoge"}'
{"post":{"id":3,"subject":"moge","body":"hoge","created_at":"2020-09-06T10:31:38.375Z","updated_at":"2020-09-06T10:31:38.375Z"}}
If rubocop and rspec work normally, commit.
I used it as a reference in this article. Thank you very much. Reference: Super easy API with Rails