Continuation of Last time.
Testing against the model. We will test model validation, class methods, and instance methods.
The model specs include at least the following three tests.
First, let's test the User model. Last time, I set the automatic generation of the spec file, but this time it is a test for the existing model, so I will generate the spec file manually.
$ rails g rspec:model user
This command will generate spec/models/user_spec.rb, so write the specs in this file.
spec/models/user_spec.rb
repuire 'rails_helper' #Loading helpers
RSpec.describe User, type: :model do #The specifications of the User model are summarized in the block
#name, email,Must be valid if you have a password
it 'is valid with a name, email, and password' do #Each spec is described in a block starting with it
user = User.new(
name: "Zeisho",
email: "[email protected]",
password: "hogehoge"
)
expect(user).to be_valid #user is valid(valid)Then pass
end
end
In RSpec, you pass the value you want to test to expect () and call the matching matcher to do the test.
In this case, we are testing user with a matcher called be_valid. Also, the to after (user) returns success if the value to be tested matches the matcher, or failed as the test result. I often use to_not, which has the opposite meaning of to, so it's a good idea to remember it.
Now that we've tested whether the model is valid when given the correct values, it's time to test if the model is in an invalid state when given data that fails validation.
spec/models/user_spec.rb
#Invalid without name
it 'is invalid without a name' do
user = User.new(
name: nil
email: "[email protected]"
password: "hogehoge"
)
user.valid?
expect(user.errors[:name]).to include("can't be blank")
end
In this spec, the validity of the user is verified by using the valid? Method with the user's name set to nil, and finally the user.errors [: name] contains "can't be blank". If it is included, it will pass. I'm testing the content of the error that occurred because I want to know if the lack of a name is the cause of the validation failure. Please note that you will not get an error message unless you verify the validity with user.valid ?.
Let's write the specifications of other validations along this method.
spec/models/user_spec.rb
#Invalid if email is duplicated
it 'is invalid without a duplicate email address' do
User.create(
neme: "zeisho"
email: "[email protected]"
password: "hogehoge"
)
user = User.new(
name: "Skywalker"
email: "[email protected]"
password: "hogehoge"
)
user.valid?
expect(user.errors[:name]).to include("has already been taken")
end
It is a spec that if you save a user using create and then generate a user with the same email, you will get a duplicate email error.
Let's complete the specifications of the User model with this condition.
When completed, we will also make specifications for the Project model.
$ rails g rspec:model project
spec/models/project_spec.rb
require 'rails_helper'
RSpec.describe Project, type: :model do
#Do not allow duplicate project names on a per-user basis
it "does not allow duplicate project names per user" do
user = User.create(
name: "Zeisho",
email: "[email protected]",
password: "hogehoge"
)
user.projects.create(
name: "Test Project"
)
new_project = user.projects.build(
name: "Test Project"
)
new_project.valid?
expect(new_project.errors[:name]).to include("has already been taken")
end
#Allow two users to use the same name
it "allows two users to share a project name" do
user = User.create(
name: "Zeisho",
email: "[email protected]",
password: "hogehoge"
)
user.projects.create(
name: "Test Project"
)
other_user = User.create(
name: "Skywalker",
email: "[email protected]",
password: "hogehoge"
)
other_project = other_user.projects.build(
name: "Test Project"
)
expect(other_project).to be_valid
end
end
Since Project is tied to User, with the current writing method, the code became redundant just by creating the instance required for testing. Problems around here will be resolved later.
In this app, there is a Note linked to the Project model, and it is possible to store a character string as a memo of the project, and the Note model implements a search function.
app/model/note.rb
scope :search, ->(term) {
where("LOWER(message) LIKE ?", "%#{term.downcase}%")
}
This time we will test this class method and scope.
$ rails g rspec:model note
spec/models/note_spec.rb
require 'rails_helper'
RSpec.describe Note, type: :model do
#Returning notes that match the search string
it "returns notes that match the search term" do
user = User.cerate(
name: "Zeisho"
email: "[email protected]"
password: "hogehoge"
)
project = user.projects.create(
name: "Test Project"
)
note1 = project.notes.create(
message: "This is first note.",
user: user
)
note2 = project.notes.create(
message: "This is second note.",
user: user
)
note3 = project.notes.create(
message: "First, preheat the oven.",
user: user
)
expect(Note.search("first")).to include(note1, note3)
expect(Note.search("first")).to_not include(note2)
end
#Return an empty collection if no search results are found
it "returns an empty collection when no results are found" do
user = User.cerate(
name: "Zeisho"
email: "[email protected]"
password: "hogehoge"
)
project = user.projects.create(
name: "Test Project"
)
note1 = project.notes.create(
message: "This is first note.",
user: user
)
note2 = project.notes.create(
message: "This is second note.",
user: user
)
note3 = project.notes.create(
message: "First, preheat the oven.",
user: user
)
expect(Note.search("message")).to be_empty
end
end
So far, we've used three matchers (be_valid, include, be_enpty), but if you want to know more about the matchers provided by RSpec, you can refer to rspec-expectations.
Use describe, context, before, after to make the spec DRY.
describe, context You can classify the spec group and put them together in a block. Let's take the specifications of the Note model as an example.
spec/models/note_spec.rb
require 'rails_helper'
RSpec.describe Note, type: :model do
#Specs for validation
#Message search function specifications
describe "search message for a term" do
#When finding matching data
context "when a match is found" do
#Example group when matching
end
#When no matching data is found
context "when no match is found" do
#Example group when they do not match
end
end
end
before, after You can put together the test data used in all tests in one place.
spec/models/note_spec.rb
require 'rails_helper'
RSpec.describe Note, type: :model do
before do
@user = User.cerate(
name: "Zeisho"
email: "[email protected]"
password: "hogehoge"
)
@project = user.projects.create(
name: "Test Project"
)
end
#Specs for validation
#Message search function specifications
end
The following options can be set for before.
before(:each) Run before each (each) test in a describe or context block
before(:all) Run only once before all tests in a describe or context block
before(suite) Run before running all files in the entire test suite
If you need to clean up after the example, you can use after.
Unlike the development / production environment, the test prioritizes readability and makes it DRY, so if you frequently scroll the editor or go back and forth between multiple files to check the contents of the spec file, It's too DRY. Consider duplicating code as needed, and try to name variables and methods that understand their role without having to go back and forth between files.
Recommended Posts