【问题标题】:Mocking a RestTemplate in Spring在 Spring 中模拟 RestTemplate
【发布时间】:2019-02-25 22:40:42
【问题描述】:

我正在尝试测试我的 spring rest 客户端,但我遇到了问题。这是我的客户端类:

@Component
public class MyClient{    

    private RestTemplate restTemplate;


    @Autowired
    public MyClient(RestTemplateBuilder restTemplateBuilder,ResponseErrorHandler myResponseErrorHandler) {
        this.restTemplate = restTemplateBuilder
                .errorHandler(myResponseErrorHandler)
                .build();
    }
    //other codes here
}

这里的myResponseErrorHandler 是覆盖ResponseErrorHandler 类的handleErrorhasError 方法的类。

现在我的测试类是

    @RunWith(SpringRunner.class)
    public class MyClientTest {    

        @InjectMocks
        MyClient myClient;
        @Mock
        RestTemplate restTemplate;
        @Mock
        RestTemplateBuilder restTemplateBuilder;

       //test cases here
    }

但我收到如下错误,我不确定如何解决此问题。

You haven't provided the instance at field declaration so I tried to construct the instance.
However the constructor or the initialization block threw an exception : null

【问题讨论】:

    标签: junit mockito resttemplate


    【解决方案1】:

    这个问题是因为当你正常运行你的应用程序时,Spring Boot会自动为你配置RestTemplateBuilder(因为@SpringBootApplication注解),但是在你的测试中你没有对应的@SpringBootTest注解,而@987654325 MyClient 的构造函数中的 @ 当然是 null (在尝试对其调用 build() 方法时会产生错误)。

    如果按原样添加,它将使用应用程序默认配置上下文,RestTemplateBuilderMyResponseErrorHandler 都将是工作 bean(请注意,在这种情况下,MyClientMyResponseErrorHandler 都应该是配置为 bean - f.e. 用 @Component 标记它们。

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class MyClientTest {
    
        @Autowired
        private MyClient myClient;
    
        @Mock
        private MyResponseErrorHandler myResponseErrorHandler;
    
        @Test
        public void sampleTest() throws IOException {
            //The example of configuring myResponseErrorHandler behavior
            Mockito.when(myResponseErrorHandler.hasError(Mockito.any())).thenReturn(true);
    
            //Call myClient method
    
            //Asserts...
        }
    }
    

    另外,不要对RestTemplate 做任何事情,因为它将在 MyClient 构造函数中以编程方式创建。

    另一种方法是创建一个单独的测试配置,您可以在其中获取默认的RestTemplateBuilder bean(由 Spring 提供)和模拟的MyResponseErrorHandler(用于稍后配置其行为)。它可以让您完全控制如何在不使用应用上下文的情况下配置所有 bean 进行测试。

    如果您想深入了解它 - 以下是实现此目的的步骤:

    1. 使用MyResponseErrorHandler bean 创建测试配置类:
    @TestConfiguration
    public class MyClientTestConfiguration {
    
        @Autowired
        private RestTemplateBuilder restTemplateBuilder;
    
        @Bean
        public ResponseErrorHandler myResponseErrorHandler() {
            return Mockito.mock(MyResponseErrorHandler.class);
        }
    
    
        @Bean
        public MyClient myClient() {
            return new MyClient(restTemplateBuilder, myResponseErrorHandler());
        }
    }
    
    1. 你的测试类应该是这样的:
    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = MyClientTestConfiguration.class)
    public class MyClientTest {
    
        @Autowired //wired from MyClientTestConfiguration class
        private MyClient myClient;
    
        @Autowired //wired from MyClientTestConfiguration class
        private MyResponseErrorHandler myResponseErrorHandler; 
    
        @Test
        public void sampleTest() throws IOException {
            //The example of configuring myResponseErrorHandler behavior
            Mockito.when(myResponseErrorHandler.hasError(Mockito.any())).thenReturn(true);
    
            //Calling myClient method...
    
            //Asserts...
        }
    }
    

    【讨论】:

    • 我不想为测试用例创建单独的配置,并且您的第一种方法无法提供空点异常。
    • 你有没有用@Component注解MyResponseErrorHandler和MyClient?此外,您最好将示例项目上传到 Github 并发布链接,而不是提供大多数无用的 cmets,例如“它不起作用”、“我认为这不会起作用”、“那没有帮助”等。事实上,这里的所有 3 个答案都可以帮助解决您的问题。
    • 顺便说一句,我也创建了示例工作项目,它绝对符合您的问题。你可以检查一下。 github.com/amseager/myclient
    • 首先我不能在github上分享我公司的代码。其次,您不是在嘲笑并获得 myClient 的真实实例。现在我的测试用例将不再是测试,他们将做真实的事情。我的测试用例会将数据保存到数据库中??对这种方法不满意。
    • restTemplate 是在构造函数内部配置的私有字段。使用 PowerMockito 之类的库可以模拟私有字段,但在您的情况下,它总是会导致有 2 个 restTemplate 实例(一个是模拟的,一个是在构造函数中构建的),这显然是无用的。但是您可以通过提供模拟的 MyClient 构造函数属性来影响构建所需的 restTemplate (因此 myClient 不再是真实的)。
    【解决方案2】:

    你有一个很好的 example 来模拟具有依赖关系的自动装配服务,

    在您的情况下,您还需要模拟 ResponseErrorHandler:

    @RunWith(MockitoJUnitRunner.class)
    public class MyClientTest {
    
    @Mock
    private ResponseErrorHandler responseErrorHandler ;
    
    @Mock
    private RestTemplateBuilder restTemplateBuilder ;
    
    private MyClient myClient;
    
    @Before
    void setUp() {
        myClient = new MyClient(restTemplateBuilder ,responseErrorHandler );
    }
    

    【讨论】:

    • 我能做到的那部分。但是一旦它使用两个模拟参数命中构造函数,它就会失败。我尝试过......然后......这没有帮助。
    【解决方案3】:
    @Component
    public class MyClient{    
    
    private RestTemplate restTemplate;
    
    
    @Autowired
    public MyClient(RestTemplateBuilder restTemplateBuilder,ResponseErrorHandler myResponseErrorHandler) {
        this(restTemplateBuilder
                .errorHandler(myResponseErrorHandler)
                .build());
    }
    
    MyClient(RestTemplate template) {
        this.restTemplate = template;
    }
    //other codes here
    }
    

    下面这个例子是正常运行时的。修改为使用SpringRunner.class

    公共类 MyClientTest {

       private MyClient myClient;
       @Mock private RestTemplate restTemplate;
    
       @Before
       public void setUp() throws Exception {
           MockitoAnnotations.initMocks(this);
           myClient = new MyClient(restTemplate);
       }
    
       //test cases here
    }
    

    【讨论】:

    • 我不知道为什么你有两个不同的构造函数。
    • 可测试性。您的被测系统可与RestTemplateRestTemplateBuilderResponseErrorHandler 一起使用?
    猜你喜欢
    • 2017-07-13
    • 2020-05-01
    • 2019-06-06
    • 1970-01-01
    • 2018-08-14
    • 2019-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多