Mock usage that comes with Spock

In the last article on the readability and maintainability of unit test code, I gave a business scenario, namely interface call. Our user service needs to call the user-centric interface to obtain user information, and the code is as follows:

/** * User services *@authorOfficial number :Java old K * personal blog :www.javakk.com */
@Service
public class UserService {

    @Autowired
    UserDao userDao;

    @Autowired
    MoneyDAO moneyDAO;

    public UserVO getUserById(int uid){
        List<UserDTO> users = userDao.getUserInfo();
        UserDTO userDTO = users.stream().filter(u -> u.getId() == uid).findFirst().orElse(null);
        UserVO userVO = new UserVO();
        if(null == userDTO){
            return userVO;
        }
        userVO.setId(userDTO.getId());
        userVO.setName(userDTO.getName());
        userVO.setSex(userDTO.getSex());
        userVO.setAge(userDTO.getAge());
        // Display the zip code
        if("Shanghai".equals(userDTO.getProvince())){
            userVO.setAbbreviation("Shanghai");
            userVO.setPostCode(200000);
        }
        if("Beijing".equals(userDTO.getProvince())){
            userVO.setAbbreviation("Beijing");
            userVO.setPostCode(100000);
        }
        // Mobile phone number processing
        if(null! = userDTO.getTelephone() && !"".equals(userDTO.getTelephone())){
            userVO.setTelephone(userDTO.getTelephone().substring(0.3) +"* * * *"+userDTO.getTelephone().substring(7));
        }

        returnuserVO; }}Copy the code

Where userDao is the instance object of the spring-injected user center service, we can proceed with the following logic (filter users by UID, DTO and VO conversion, postcode, mobile phone number processing, etc.) only after we get the returned users from the user center.

So it would be normal to mock out the getUserInfo() method of the userDao to mock out a value we specified, because what we really care about is the logic of our code once we get users, and that’s where we need to focus our verification

The following test code is written using Spock along the lines above:

package com.javakk.spock.service

import com.javakk.spock.dao.UserDao

import spock.lang.Specification
import spock.lang.Unroll

/** * User service test class *@authorOfficial number :Java old K * personal blog :www.javakk.com */
class UserServiceTest extends Specification {
    def userService = new UserService()
    def userDao = Mock(UserDao)

    void setup(a) {
        userService.userDao = userDao
    }

    def "GetUserById"() {
        given: "Set request Parameters"
        def user1 = new UserDTO(id:1, name:"Zhang", province: "Shanghai")
        def user2 = new UserDTO(id:2, name:"Bill", province: "Jiangsu")

        and: Mock out user information returned by the interface
        userDao.getUserInfo() >> [user1, user2]

        when: "Call the method to get user information"
        def response = userService.getUserById(1)

        then: "Verify that the returned result meets the expected value"
        with(response) {
            name == "Zhang"
            abbreviation == "Shanghai"
            postCode == 200000}}}Copy the code

To see how junit is implemented, see the comparison diagram in the previous article, which focuses on spock code :(from top to bottom)

Def userDao = Mock(userDao) this line of code uses spock’s built-in Mock method to construct a Mock object for the userDao. To simulate the return of the userDao method, only the userDao is required. Method name () >> simulate the value of the way, two right arrow way

The setup method is the initial method before each test case runs, similar to junit’s @before method

GetUserById method is the main method of single test. It can be seen that it is divided into four modules: Given, and, WHEN, and then, which are used to distinguish the functions of different single test codes:

  • Given: Input conditions (front parameters)
  • When: Execute behavior (mock interface, real call)
  • Then: Output conditions (validation results)
  • And: connects the previous label, supplementary function

A description of what this code does (optional) can be added in the double quotes after each tag, such as “when: “calls the method that gets user information”

Because Spock uses Groovy as a single-test development language, there is much less code to write than in Java, such as in the Given module, which uses constructors to create request objects

given: "Set request Parameters"
def user1 = new UserDTO(id:1, name:"Zhang", province: "Shanghai")
def user2 = new UserDTO(id:2, name:"Bill", province: "Jiangsu")
Copy the code

In fact, the userto. Java class does not have a three-argument constructor. Groovy does this for us. Groovy provides a constructor that contains all the object properties by default

And the call can specify the attribute name, similar to the key:value syntax, very humanized, convenient for us to construct the object in the case of many attributes, if you use Java to write, you may have to call a lot of setXXX() method to complete the object initialization work

and: Mock out user information returned by the interface
userDao.getUserInfo() >> [user1, user2]
Copy the code

This is a mock use of Spock that returns a List when the userdao.getUserInfo () method is called. Creating a List is also simple. The parentheses “[]” denote a List, and Groovy automatically matches an array or a List based on the return type of the method. The object in the list is the user object constructed in the given block

Where “>>” specifies the return result, similar to Mockito’s when().thenreturn () syntax, but more concise

To specify that multiple values are returned, you can use three right arrows “>>>”, for example:

userDao.getUserInfo() >>> [[user1,user2],[user3,user4],[user5,user6]]

It can also be written like this:

userDao.getUserInfo() >> [user1,user2] >> [user3,user4] >> [user5,user6]

That is, the userdao.getUserInfo () method returns a different value each time it is called

If the mock method takes an input parameter, as in the following business code:

public List<UserDTO> getUserInfo(String uid){
    // Simulate a user center service interface call
    List<UserDTO> users = new ArrayList<>();
    return users;
}
Copy the code

The getUserInfo(String uid) method, which takes a uid parameter, can be matched with an underscore “_” to indicate any type of parameter, separated by multiple commas, similar to mockito’s any() method

If more than one function with the same name exists in the class, it can be called separately by “_ as parameter type “, similar to the following syntax:

// _ Matches any type of parameters
List<UserDTO> users = userDao.getUserInfo(_);

// If there is a method with the same name, use as to specify the parameter type distinction
List<UserDTO> users = userDao.getUserInfo(_ as String);
Copy the code

In the when module is the entry that actually calls the method to be tested :userService.getUserById()

The then module is used to verify that the results of the methods under test are correct and conform to the expected values, so the statements in this module must be Boolean expressions, similar to junit’s assert mechanism, but you do not have to explicitly write assert. This is also the idea of convention over configuration

The then block uses Spock’s with function to verify that multiple properties within the returned response object conform to expected values, which is simpler than junit’s assertNotNull or assertEquals

Where usage

The business code above has three if judgments, which are the processing logic for zip code and mobile phone number:

// Display the zip code
if("Shanghai".equals(userDTO.getProvince())){
    userVO.setAbbreviation("Shanghai");
    userVO.setPostCode(200000);
}
if("Beijing".equals(userDTO.getProvince())){
    userVO.setAbbreviation("Beijing");
    userVO.setPostCode(100000);
}
// Mobile phone number processing
if(null! = userDTO.getTelephone() && !"".equals(userDTO.getTelephone())){
    userVO.setTelephone(userDTO.getTelephone().substring(0.3) +"* * * *"+userDTO.getTelephone().substring(7));
}
Copy the code

In order to fully cover the three branches, a unit test now needs to construct different request parameters and call the method under test multiple times to go to the different branches. Spock’s WHERE tag can do this very easily, as shown in the following code:

@Unroll
def "When the user ID is #uid, the zip code is returned :#postCodeResult, and the phone number is #telephoneResult."() {
    given: Mock out user information returned by the interface
    userDao.getUserInfo() >> users

    when: "Call the method to get user information"
    def response = userService.getUserById(uid)

    then: "Verify that the returned result meets the expected value"
    with(response) {
        postCode == postCodeResult
        telephone == telephoneResult
    }

    where: "Branching scenarios for tabular validation of user information"
    uid | users                         || postCodeResult | telephoneResult
    1   | getUser("Shanghai"."13866667777") | |200000         | "138 * * * * 7777"
    1   | getUser("Beijing"."13811112222") | |100000         | "138 * * * * 2222"
    2   | getUser("Nanjing"."13833334444") | |0              | null
}

def getUser(String province, String telephone){
    return [new UserDTO(id: 1, name: "Zhang", province: province, telephone: telephone)]
}
Copy the code

Where module is the first line of code table column name, use more than one column “|” divided by a single vertical lines, “| |” double vertical lines to distinguish between the input and output variables, that is, on the left is the input values, the right is the output value

The format is as follows:

Input parameters input 1 | 2 | | | output 1 output 2

And Intellij IDEA supports the format shortcut key, because the table column length is not the same, manual alignment is more troublesome

Each row of the table represents a test case in which the method under test is tested three times, with different inputs and outputs each time, just enough to cover all branches

For example, uid and Users are input conditions. The construction of the Users object calls the getUser method. Each time the test business code passes in a different user value, postCodeResult and telephoneResult indicate whether the attribute of the returned response object is correct

The first row verifies whether the zip code returned is “200000”, the second row verifies whether the zip code returned is “100000”, and the third row verifies whether the zip code returned is “0”.

This is the use of where+with, more in line with our actual test scenario, can cover multiple branches, and can verify the properties of complex objects

The test method name defined in line 2 uses groovy’s literal feature:

@Unroll
def "When the user ID is #uid, the zip code is returned :#postCodeResult, and the phone number is #telephoneResult."() {
Copy the code

#uid #postCodeResult #telephoneResult #uid #postCodeResult #telephoneResult

The @unroll annotation allows each call to be run as a separate test case, which makes the single test result more intuitive:

Spock’s error message is very detailed and easy to detect (for example, we changed the zip code returned by test case # 2 to “100001”) :

The second test case fails. The error message is that the postCodeResult’s expected result does not match the actual result. The business code logic returns a zip code of “100000”, whereas our expected zip code is “100001”, so you can check whether the business code logic is wrong or our assertion is wrong.

This example shows the advantages of Spock in combination with the Groovy language when testing multiple branching scenarios.

(The full source code is available in spock’s reply.)

Source: javakk.com/273.html