JUnit 测试模拟对象返回空值

JUnit Tests Mocked Objects are returning null values

我正在尝试 运行 在使用配置 Class 和 RestTemplate Class 的服务层上进行 JUnit 测试。但是,每当我尝试 运行 测试时,我的配置和服务方法的 return 值都会得到空值。

这是我的服务class:

package com.blogposts.assessment.restapi;

import com.blogposts.assessment.classes.Post;
import com.blogposts.assessment.classes.Posts;
import com.blogposts.assessment.exceptions.BadInputException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/*
This is a Service component in Spring Boot that implements the business logic for the api. It de-duplicates posts in
the list and implements sort based on the parameters received from the rest controller.
 */

@Service
public class APIService {

    private final RestTemplate restTemplate;
    private final APIConfiguration apiConfig;

    //This looks for the beans that were created for the APIConfiguration and the RestTemplate so it utilizes
    //the same instance of those beans.
    @Autowired
    public APIService(RestTemplate restTemplate, APIConfiguration apiConfig) {
        this.restTemplate = restTemplate;
        this.apiConfig = apiConfig;
    }

    //Implements business logic on Get Request
    public Posts getBlogPosts(String tags, String sortBy, String direction) throws ExecutionException, InterruptedException {

        Posts consolidatedPosts = getBlogPostsWithMultipleTags(tags);
        List<Post> listOfPosts = consolidatedPosts.getPosts();

        //If direction is "asc" or blank, it sorts in an ascending manner
        if (direction.equals("asc") || direction.equals("")) {

            //Switch used to select how to sort
            switch (sortBy) {
                case "id":
                    Collections.sort(listOfPosts, Post.compareById);
                    break;
                case "":
                    Collections.sort(listOfPosts, Post.compareById);
                    break;
                case "likes":
                    Collections.sort(listOfPosts, Post.compareByLikes);
                    break;
                case "reads":
                    Collections.sort(listOfPosts, Post.compareByReads);
                    break;
                case "popularity":
                    Collections.sort(listOfPosts, Post.compareByPopularity);
                    break;
                default:
                    throw new BadInputException("sortBy parameter is invalid");
            }
        }

        //If direction is "desc", posts are sorted in reverse order.
        else if (direction.equals("desc")) {
            switch (sortBy) {
                case "id":
                    Collections.sort(listOfPosts, Post.compareById.reversed());
                    break;
                case "":
                    Collections.sort(listOfPosts, Post.compareById.reversed());
                    break;
                case "likes":
                    Collections.sort(listOfPosts, Post.compareByLikes.reversed());
                    break;
                case "reads":
                    Collections.sort(listOfPosts, Post.compareByReads.reversed());
                    break;
                case "popularity":
                    Collections.sort(listOfPosts, Post.compareByPopularity.reversed());
                    break;
                default:
                    throw new BadInputException("sortBy parameter is invalid");
            }
        }

        //Throws exception if direction parameter is not desc, asc, or blank.
        else {
            throw new BadInputException("direction parameter is invalid");
        }
        return consolidatedPosts;
    }

    //This method conducts multiple get requests to the Hatchways API based on the number of tags included and
    //de-duplicates the posts so that no post is repeated.
    public Posts getBlogPostsWithMultipleTags(String tags) throws ExecutionException, InterruptedException {

        //Checks to see if the tags parameter is left blank or is null
        if(tags.equals("")) {
            throw new BadInputException("The tags parameter is missing and cannot be null.");
        }

        //Splits the comma separated tags into an array of Strings
        String[] tagsArray = tags.split(",");

        //Utilizing a hashset to store each posts' id if it's added to the Posts object's List.
        //Hashset was used because lookup time is O(1) with the blog id value.
        Posts combinedPosts = new Posts();
        Posts[] separatePosts = new Posts[tagsArray.length];
        HashSet<Long> consolidatedPostIDs = new HashSet<Long>();

        //Iterate through each tag and run a get request to the Hatchways API
        for (int i = 0; i < tagsArray.length; i++) {
            String url = apiConfig.getApiUrl() + "/?tag=" + tagsArray[i];
            CompletableFuture<Posts> postsFuture = getBlogPostWithOneTag(tagsArray[i]);
            separatePosts[i] = postsFuture.get();
        }

        // Loop through the Posts[] array to review each Posts object. Look at an individual post within each Posts object
        // For each blog individual post, check to see if it's already in our combinedPosts object. If it is, ignore it, otherwise add
        // it to the hashset and the combinedPosts object.
        for (Posts posts : separatePosts) {
            for(int i = 0; i < posts.getPosts().size(); i++) {
                Post currentPost = posts.getPosts().get(i);
                if(consolidatedPostIDs.contains(currentPost.getId())) {
                    continue;
                } else {
                    combinedPosts.addPost(currentPost);
                    consolidatedPostIDs.add(currentPost.getId());
                }
            }
        }

        return combinedPosts;
    }

    @Async
    public CompletableFuture<Posts> getBlogPostWithOneTag(String tag){
        String url = apiConfig.getApiUrl() + "/?tag=" + tag;
        Posts posts = restTemplate.getForObject(url,Posts.class);
        return CompletableFuture.completedFuture(posts);
    }

}

我正在尝试使用 Junit 测试和 Mockito 对此进行测试,但是当 canGetBlogPosts() 测试为 运行 时,它会为两个 System.out.println() 调用打印出空值。

package com.blogposts.assessment.restapi;

import com.blogposts.assessment.classes.Posts;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import static org.junit.jupiter.api.Assertions.assertNotNull;

@ExtendWith(MockitoExtension.class)
public class APIServiceTest {

    @Mock
    private RestTemplate restTemplate;

    @Mock
    private APIConfiguration apiConfiguration;
    private APIService underTest;

    @BeforeEach
    void setUp() {
        underTest = new APIService(restTemplate,apiConfiguration);
    }

    @Test
    void canGetBlogPosts() throws ExecutionException, InterruptedException {
        //when
        System.out.println(apiConfiguration.getApiUrl());
        CompletableFuture<Posts> posts = underTest.getBlogPostWithOneTag("science");
        System.out.println(posts.get());

    }

    @Test
    @Disabled
    void getBlogPostsWithMultipleTags() {
    }

    @Test
    @Disabled
    void getBlogPostWithOneTag() {
    }
}

很简单。您正在创建您的模拟,但您没有使用特殊的 mockito when-then 语法配置它们。

以下是您的操作方法。

如果您希望模拟在每个测试中都以相同的方式运行,则可以在 @BeforeEach 方法中执行此操作。或者您可以为每个测试单独配置它们。

这是基本语法。您可以获取详细信息 here.

List mockList = Mockito.mock(ArrayList.class);
Mockito.when(mockList.size()).thenReturn(100);

此外,您可能需要添加它以获取由 MockitoExtension

注入的模拟
@Before
public void init() {
    MockitoAnnotations.initMocks(this);
}

这能解决您的问题吗?在评论中让我知道。