[RUBY] Mock and stub in RSpec

References Everyday Rails-Introduction to Rails Testing with RSpec https://leanpub.com/everydayrailsrspec-jp

Mock and stub

mock

An object that pretends to be a real object used for testing. It is also called a test double. The mock does not access the database, which reduces test time.

stub

Overrides the object's methods and returns a predetermined value. A stub is a dummy method that, when called, returns a real result for testing. Stubs are often used to override the default functionality of a method. In particular, processing that uses databases and networks is targeted.

I will actually write

The name (combination of first_name + last_name) method in the User object is delegated to the Note object and attributes are added.

User attributes first_name and last_name ↓ Combine first_name and last_name with User's name method ↓ Add the combined value as user_name to the Note attribute

Test if this is working properly.

First code.rb


it "Delegate getting the name to the user who created the note" do
 user = FactoryBot.create(:user, first_name: "Fake", last_name: "User")
 note = Note.new(user: user)
 expect(note.user_name).to eq "Fake User"
end

In this code User object needs to be saved in DB and made persistent. As the number and complexity increase, all the uninteresting processing swells, even though it's a Note model test. And in the Note model test, I know too much about the implementation of the User model. This test is not interested in the fact that the name in the User model has the attributes first_name and last_name. All you have to do is know that the User model returns the string name.

Based on this, refactoring will be performed.

Refactored code.rb


it "Delegate getting the name to the user who created the note" do
 #Replace User object with double(mock)
 user = double("user", name: "Fake User")
 note = Note.new
  #note.A stub that explicitly states that you want to call user
 allow(note).to receive(:user).and_return(user)
 expect(note.user_name).to eq "Fake User"
end

Replaced the persistence of the User object with a mock. Since we want the mock object to have a method, Key and Value are specified after the second argument. (User's name method returns "Fake User") If you look at this object, you'll see a class named Double. Note that this mock is an object that only knows how to return a name. Of course, if you try to test first_name as shown below, you will get an error because you cannot return a value.

expect(note.user.first_name).to eq "Fake"

Next, let's look at the stub. The stub is created with allow. (Use allow (obj) .to receive (: method) if the receiver object is not a mock object but an existing object) The writing method is shown below.

#and_The return value of the method is specified by return.
allow(obj).to receive(:method).and_return(true)

The refactored code tells me to call note.user somewhere in the test.

allow(note).to receive(:user).and_return(user)

When user.name is actually called, the value of note.user_id is used to search for the appropriate user in the database, and instead of returning the found user, the result is a test double named user.

Click here for a very easy-to-understand article Developers.IO https://dev.classmethod.jp/articles/rspec-recipe/ An introduction to RSpec that can be used, part 3 "How to write a test using a mock that can be understood from zero" https://qiita.com/jnchito/items/640f17e124ab263a54dd

Now you don't have to save the User object in the DB and make it persistent. And for the Note model test, I was able to refactor it with minimal interest in the User model. However, there are still problems.

Test double with verification function

RSpec's test double does not verify that the object you are trying to substitute has a method that you want to stub. In other words, the method you call on the stub will be undefined, but the test will pass. When using a test double as much as possible, it is desirable to use a test double with a verification function. Therefore, change to a verified double with a verification function.

Code changed to double with verification.rb


it "Delegate getting the name to the user who created the note" do
 #instance_Change to double. Change the first letter of user to uppercase
 user = instance_double("User", name: "Fake User")
 note = Note.new
 allow(note).to receive(:user).and_return(user)
 expect(note.user_name).to eq "Fake User"
end

It's basically the same as double, except that you get an error when you stub an undefined instance method. Therefore, it is possible to write a test while confirming that the method exists.

Thank you for staying with us until the end. If you have any suggestions or advice, I would appreciate it if you could comment.

Recommended Posts

Mock and stub in RSpec
Mock and stub with minitest (use RR, WebMock, MiniTest :: Mock)
[Rails] How to define macros in Rspec and standardize processing
Mock and spy on PowerMock
Matcher often used in RSpec
Mock static methods in Mockito 3.4
[Wire Mock] I want to set up a stub / mock server in Java and perform E2E tests.
If you want to mock a method in RSpec, you should use the allow method for mock and the singleton method.
Enable jQuery and Bootstrap in Rails 6 (Rails 6)
Encoding and Decoding example in Java
Try running ruby-net-nntp and tmail in 2020
Rough difference between RSpec and minitest
CGI in C and Dart: Introduction (1)
[Ruby] then keyword and case in
StringBuffer and StringBuilder Class in Java
Write keys and values in Ruby
Remove "assets" and "turbolinks" in "Rails6".
CRUD features and MVC in Rails
Understanding equals and hashCode in Java
Copy and paste test with RSpec
Hello world in Java and Gradle
How to test a private method in Java and partially mock that method