[RUBY] Building a bulletin board API with authentication authorization with Rails 6 Validation and test implementation of # 4 post

Building a bulletin board API with authentication authorization with Rails 6 # 3 RSpec, FactoryBot is introduced and a post model is created

Write model tests in RSpec

For the time being, I was able to confirm that create and read work last time. From here, follow this procedure

  1. Write a model test for post
  2. Implement validation
  3. Write a post controller test
  4. Write controller and routes
  5. Write the seed

In this article, I will implement 1 and 2 for the time being, and after 3. I will proceed with the next and subsequent articles.

Crush rubocop in advance

Write the documentation Exclude errors that also appear in the migration file.

.rubocop.yml can be excluded in this way, but if you exclude what you originally need, it will break the meaning of observing the coding rules in the first place, so let's discuss it firmly when adding it in team development. ..

diff:.rubocop.yml


+ #documentation
+ Style/Documentation:
+  Exclude:
+    - "db/migrate/**/*"
...

First write a model test

Like Test Driven Development (TDD), the first is Red's test. Since validation is not implemented, I will make something that will be Red even if I write a test and run it.

I will write it in a normal Rails tick without using factory_bot once.

spec/models/post_spec.rb


# frozen_string_literal: true

require "rails_helper"

RSpec.describe Post, type: :model do
  describe "subject" do
    context "When blank" do
      it "Become invalid" do
        post = Post.new(subject: "", body: "fuga")
        expect(post).not_to be_valid
      end
    end
  end
end

So the above code is testing that "it becomes invalid when subject is blank". But so far I haven't validated the subject of the post model so the post is valid and the "invalid" test will fail.

$ rspec spec/models/post_spec.rb
...
Finished in 0.07805 seconds (files took 3.53 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/models/post_spec.rb:8 #Invalid when Post subject blank

ec2-user:~/environment/bbs (master) $ rspec

Let's try to register even if the subject is empty.

$ rails c
[1] pry(main)> Post.create!(subject: "", body: "hoge")
   (0.1ms)  BEGIN
  Post Create (2.5ms)  INSERT INTO "posts" ("subject", "body", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["subject", ""], ["body", "hoge"], ["created_at", "2020-09-06 01:07:52.628768"], ["updated_at", "2020-09-06 01:07:52.628768"]]
   (0.9ms)  COMMIT
=> #<Post:0x0000000005760700
 id: 2,
 subject: "",
 body: "hoge",
 created_at: Sun, 06 Sep 2020 01:07:52 UTC +00:00,
 updated_at: Sun, 06 Sep 2020 01:07:52 UTC +00:00>

You have saved it.

By the way, without using describe or context

spec/models/post_spec.rb


# frozen_string_literal: true

require "rails_helper"

RSpec.describe Post, type: :model do
  it "Invalid when subject is blank" do
    post = Post.new(subject: "", body: "fuga")
    expect(post).not_to be_valid
  end
end

The behavior of the test code is almost the same. This is because you can test by writing expect in the it block. However, it becomes difficult to understand when describing the same column and the same validation conditions by grouping, so basically it is recommended to describe by nesting describe and context.

Add validation to model

Validation with blank as an error

app/models/post.rb


 class Post < ApplicationRecord
+  validates :subject, presence: true
 end

Now you can't register with true, that is, blank for presence for the subject column.

Let's try it.

$ rails c
[1] pry(main)> Post.create!(subject: "", body: "hoge")
ActiveRecord::RecordInvalid: Validation failed: Subject can't be blank
from /home/ec2-user/.rvm/gems/ruby-2.7.1/gems/activerecord-6.0.3.2/lib/active_record/validations.rb:80:in `raise_validation_error'

I can't register.

$ rspec ./spec/models/post_spec.rb 
...
Finished in 0.05053 seconds (files took 1.63 seconds to load)
1 example, 0 failures

The test also passed.

Validation of maximum number of characters

Since it is a problem if the number of characters can be registered infinitely, we will add a limit. This is also from the test first. I will write a test with a plan to add validation that it is OK if it is 30 characters or less and NG if it is 31 characters or more.

spec/models/post_spec.rb


         expect(post).not_to be_valid
       end
     end
+    context "by maxlength" do
+      context "For 30 characters" do
+        it "Become valid" do
+          post = Post.new(subject: "Ah" * 30, body: "fuga")
+          expect(post).to be_valid
+        end
+      end
+      context "For 31 characters" do
+        it "Become invalid" do
+          post = Post.new(subject: "Ah" * 31, body: "fuga")
+          expect(post).not_to be_valid
+        end
+      end
+    end
   end
 end

Let's run the test.

$ rspec ./spec/models/post_spec.rb 
...
Finished in 0.03204 seconds (files took 1.42 seconds to load)
3 examples, 1 failure

Failed examples:

rspec ./spec/models/post_spec.rb:21 #Post subject maxlength makes it invalid for 31 characters

I haven't added validation yet, so 30 characters will pass but 31 characters will be moss. Add validation to model.

app/models/post.rb


 class Post < ApplicationRecord
-  validates :subject, presence: true
+  validates :subject, presence: true, length: { maximum: 30 }
 end
$ rspec ./spec/models/post_spec.rb 
...
Finished in 0.02201 seconds (files took 1.4 seconds to load)
3 examples, 0 failures

You passed the test. This will result in an error at 31 characters. Try it with rails c.

Replace with FactoryBot

For example, in the test code above

  post = Post.new(subject: "Ah" * 30, body: "fuga")

However, it is troublesome to specify the body every time. If it's 2 columns, it's still okay, but if it exceeds 10 columns, the code will be unnecessarily long. At that time, use factoryBot.

The factoryBot looks under spec / factories /. This time, there is no need to change the initial value when the model was created, but let's take a look at the contents.

spec/factories/posts.rb


# frozen_string_literal: true

FactoryBot.define do
  factory :post do
    subject { "MyString" }
    body { "MyText" }
  end
end

Edit the post_spec.rb file.

spec/models/post_spec.rb


   describe "subject" do
     context "When blank" do
       it "Become invalid" do
-        post = Post.new(subject: "", body: "fuga")
+        post = build(:post, subject: "")
         expect(post).not_to be_valid
       end
     end
     context "by maxlength" do
       context "For 30 characters" do
         it "Become valid" do
-          post = Post.new(subject: "Ah" * 30, body: "fuga")
+          post = build(:post, subject: "Ah" * 30)
           expect(post).to be_valid
         end
       end
       context "For 31 characters" do
         it "Become invalid" do
-          post = Post.new(subject: "Ah" * 31, body: "fuga")
+          post = build(:post, subject: "Ah" * 31)
           expect(post).not_to be_valid
         end
       end

build is the equivalent of .new using factoryBot. It is not saved to the database. In this case, subject is specified, but body is not specified, so " MyText " is entered in the body of factoryBot.

Also, run a test for each change to make sure it's OK.

Replace variable with let

For the time being, try changing it as follows.

spec/models/post_spec.rb


 RSpec.describe Post, type: :model do
   describe "subject" do
     context "When blank" do
+      let(:post) do
+        build(:post, subject: "")
+      end
       it "Become invalid" do
-        post = build(:post, subject: "")
         expect(post).not_to be_valid
       end
     end
     context "by maxlength" do
       context "For 30 characters" do
+        let(:post) do
+          build(:post, subject: "Ah" * 30)
+        end
         it "Become valid" do
-          post = build(:post, subject: "Ah" * 30)
           expect(post).to be_valid
         end
       end
       context "For 31 characters" do
+        let(:post) do
+          build(:post, subject: "Ah" * 31)
+        end
         it "Become invalid" do
-          post = build(:post, subject: "Ah" * 31)
           expect(post).not_to be_valid
         end
       end

let is a variable that is limited to the scope within the same describe or context block. In Ruby, the last evaluated expression is the return value, so

  let(:post) do
    build(:post, subject: "Ah" * 31)
  end

In the case of, the post of the build execution result becomes a variable called post by let (: post).

Exercise

Let's implement the required limit / 100 character limit test and validation on the body.

body implementation answer example

spec/models/post_spec.rb


# frozen_string_literal: true

require "rails_helper"

RSpec.describe Post, type: :model do
  describe "subject" do
    context "When blank" do
      let(:post) do
        build(:post, subject: "")
      end
      it "Become invalid" do
        expect(post).not_to be_valid
      end
    end
    context "by maxlength" do
      context "For 30 characters" do
        let(:post) do
          build(:post, subject: "Ah" * 30)
        end
        it "Become valid" do
          expect(post).to be_valid
        end
      end
      context "For 31 characters" do
        let(:post) do
          build(:post, subject: "Ah" * 31)
        end
        it "Become invalid" do
          expect(post).not_to be_valid
        end
      end
    end
  end

  describe "body" do
    context "When blank" do
      let(:post) do
        build(:post, body: "")
      end
      it "Become invalid" do
        expect(post).not_to be_valid
      end
    end
    context "by maxlength" do
      context "For 100 characters" do
        let(:post) do
          build(:post, body: "Ah" * 100)
        end
        it "Become valid" do
          expect(post).to be_valid
        end
      end
      context "For 101 characters" do
        let(:post) do
          build(:post, body: "Ah" * 101)
        end
        it "Become invalid" do
          expect(post).not_to be_valid
        end
      end
    end
  end
end

If you run rspec at this point, it will be moss

app/models/post.rb


# frozen_string_literal: true

#
#Post class
#
class Post < ApplicationRecord
  validates :subject, presence: true, length: { maximum: 30 }
  validates :body, presence: true, length: { maximum: 100 }
end

Exclusion setting because rubocop is moss. It is better not to be too strict because the test may be counterproductive if you comply with DRY and coding standards.

diff:.rubocop.yml


+ #Block length
+ Metrics/BlockLength:
+   Exclude:
+     - "spec/**/*"

At this point, run rspec, rubocop and it will pass

Continued

Building a bulletin board API with authentication authorization with Rails 6 # 5 controller, routes implementation

[To the serial table of contents]

Recommended Posts

Building a bulletin board API with authentication authorization with Rails 6 Validation and test implementation of # 4 post
Build a bulletin board API with authentication and authorization with Rails # 18 ・ Implementation of final user controller
Build a bulletin board API with authentication authorization in Rails # 12 Association of user and post
Build a bulletin board API with authentication authorization in Rails 6 # 11 User model test and validation added
Build a bulletin board API with authentication authorization with Rails 6 # 3 RSpec, FactoryBot introduced and post model
Build a bulletin board API with authentication and authorization with Rails 6 # 1 Environment construction
Build a bulletin board API with authentication authorization in Rails 6 # 5 controller, routes implementation
Build a bulletin board API with authentication authorization with Rails 6 # 2 Introducing git and rubocop
# 8 seed implementation to build bulletin board API with authentication authorization in Rails 6
Build a bulletin board API with authentication authorization in Rails # 13 Add authentication header
# 6 show, create implementation to build bulletin board API with authentication authorization in Rails 6
Introduced # 10 devise_token_auth to build a bulletin board API with authentication authorization in Rails 6
Introducing # 15 pundit to build a bulletin board API with authentication authorization in Rails 6
Build a bulletin board API with authentication authorization in Rails # 17 Add administrator privileges
# 7 update, destroy implementation to build bulletin board API with authentication authorization in Rails 6
Build a bulletin board API with authentication authorization in Rails 6 # 14 seed Execution time display
# 16 policy setting to build bulletin board API with authentication authorization in Rails 6
Introduced # 9 serializer to build bulletin board API with authentication authorization in Rails 6
[Rails] Implementation of drag and drop function (with effect)
I implemented Rails API with TDD by RSpec. part3-Action implementation with authentication-
I tried to make a group function (bulletin board) with Rails
I implemented Rails API with TDD by RSpec. part1-Action implementation without authentication-
Create a SPA with authentication function with Rails API mode + devise_token_auth + Vue.js 3 (Rails edition)
Building Rails 6 and PostgreSQL environment with Docker
[Rails] Implementation of validation that maintains uniqueness
[Rails] Set validation for the search function using Rakuten API (from the implementation of Rakuten API)
I tried to express the phone number (landline / mobile phone) with a regular expression in Rails and write validation and test
Create a simple bulletin board with Java + MySQL
[Nuxt / Rails] POST implementation using axios and devise_token_auth
How to build API with GraphQL and Rails