【问题标题】:How Do I Mock ResteTemplate exchange without service我如何在没有服务的情况下模拟 RestTemplate 交换
【发布时间】:2019-05-22 19:11:05
【问题描述】:

我有一个静态类,它将转到另一个项目,在我的应用程序启动时获取我需要的信息,因为这个项目有多个子项目,而其他项目需要这些信息来处理,所以我必须使它成为静态和因为我只希望一个实例存在跨项目,所以我将构造函数设为私有,所以当其他项目需要这个信息列表时,他们只能使用这个 class.getinstance().getList() 来获取所有信息,这将强制其他项目使用相同的信息列出交叉子项目。我确实想过尝试将这个类放在Web服务项目中,然后一切都可以用Autowired完成,但问题是在其他子项目中,他们无法接受Autowired跨项目,所以它制作了我的信息列表收集的只能在 Web 服务项目中访问,这个类位于实用程序项目中,所以在我的 Web 服务项目中,我有一个服务来初始化这个类,这个类应该只初始化一次,所以我给它一个私有构造函数获取实例方法,因此 Web 服务类将调用此类中的配置方法来对其进行初始化,并使用 restTemplate 运行 Web 服务调用以收集我需要的信息,因此该类上面没有服务类,现在我想为了测试这个类,我用 springRunner 为它做了一个模拟测试,但它似乎没有达到我的模拟数据。

我已经尝试了几种我在谷歌上找到的解决方案,似乎它们都与我的情况略有不同,他们@InjectMocksof 服务类并模拟 RestTemplat,但我的情况它不需要服务类,并且由于它的私有构造函数我可以也不注入。

这是我在 Utilities 项目中的课程。

public class InfoBook
{
//private constructor and config method to set username password and endpoint etc
protected Info LoadInfo()
  {

    final RestTemplate restTemplate = new RestTemplate();

    final HttpHeaders headers = new HttpHeaders();
    headers.setBasicAuth(username, password);
    final HttpEntity<String> request = new HttpEntity<>(headers);

    ResponseEntity<List<InfoResource>> response = null;
    try
    {
      response = restTemplate.exchange(wsEndPoint,
          HttpMethod.GET, request, new ParameterizedTypeReference<List<InfoResource>>()
          {
          });
    }
    catch (final RestClientException e)
    {
      //Catch Exception if anything happened during make the rest request, such as connection refused etc.

    }

    Info info = null;
    if (response != null)
    {
      final List<InfoResource> informationList = response.getBody();
      info = InformationMapper.INSTANCE.inforResourceToInfo(informationList.get(0));
    }

    return info ;
  }
}

这是我做的测试:

@RunWith(SpringRunner.class)
public class InfoBookTest
{
  @Mock
  private RestTemplate restTemplate;

  @Before
  public void setUp()
  {
InfoBook.configInstance("username", "password", "http://localhost:8080");
     List<InfoResource> informationList = new ArrayList<>();
     InfoResource infoResource = new InfoResource();
     // Set content
     informationList.add(infoResource);

     ResponseEntity<List<InfoResource>> response = new ResponseEntity<>(informationList, HttpStatus.OK);

     Mockito.when(restTemplate.exchange(ArgumentMatchers.any(URI.class),
        ArgumentMatchers.any(HttpMethod.class), ArgumentMatchers.<HttpEntity<String>> any(),
        ArgumentMatchers.<Class<List<InfoResource>>> any())).thenReturn(response);
  }

  @Test
  public void testloadInfo()
  {
    final Info info=
        InfoBook.getInstance().loadInfo();
    Assert.assertEquals(1000, info.getInfoId());
  }
}

现在,如果我运行此测试,它会尝试对 localhost:8080 进行 Web 服务调用,当然它会收到连接被拒绝的错误。它似乎没有击中我的 Mockito 时然后返回。 谁能告诉我如何模拟这个?

谢谢

【问题讨论】:

    标签: spring spring-mvc mockito springmockito


    【解决方案1】:

    你的类有一个 RestTemplate 的 final 字段,它用一个新的、真实的 RestTemplate 进行初始化。实际上,您没有用于测试的接缝,因此目前这是不可能的。您需要在 InfoBook 中添加一个字段和设置器,您可以从您的测试中调用它,或者更好地使 InfoBook 成为一个服务并使用一个 bean 和一个自动装配的字段。 InfoBook 不能再是单例了,但 Spring 无论如何只会创建一个,所以它实际上是一个,它只是不能有 static getInstance(),它需要在任何使用它的地方自动装配。

    @Service
    public class InfoBook {
        @Autowired
        private RestTemplate restTemplate;
    
        //private constructor and config method to set username password and endpoint etc
        protected Info LoadInfo() {
    
            final HttpHeaders headers = new HttpHeaders();
            headers.setBasicAuth(username, password);
            final HttpEntity<String> request = new HttpEntity<>(headers);
    
            ResponseEntity<List<InfoResource>> response = null;
            try {
                response = restTemplate.exchange(wsEndPoint,
                        HttpMethod.GET, request, new ParameterizedTypeReference<List<InfoResource>>() {
                        });
            } catch (final RestClientException e) {
                //Catch Exception if anything happened during make the rest request, such as connection refused etc.
    
            }
    
            Info info = null;
            if (response != null) {
                final List<InfoResource> informationList = response.getBody();
                info = InformationMapper.INSTANCE.inforResourceToInfo(informationList.get(0));
            }
    
            return info;
        }
    }
    

    你需要一个配置类来创建RestTemplate bean

    @Configuration
    public class RestTemplateConfig {
    
        @Bean
        public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){
            return restTemplateBuilder.build();
        }
    }
    

    最后你可以只使用 Mockito 运行器进行测试,你需要使用 @InjectMocks 来创建 InfoBook。

    @RunWith(MockitoJUnitRunner.class)
    public class InfoBookTest
    {
      @InjectMocks
      private InfoBook infoBook;
      @Mock
      private RestTemplate restTemplate;
    ...
    

    【讨论】:

    • 感谢 DCTID 的回答,但我需要它是静态单例,因为它还需要用于其他具有依赖关系的 maven 子项目中,包括我的 Web 服务项目等,我不确定自动连接是否会是否正在跨子项目工作,这就是为什么我一开始没有使用自动连接,我可以将我的 restTemplate 移到我的方法之外并为其创建一个 get 方法,所以在我的测试中我可以获得相同的 restTemplate 实例,但在那之后我不断得到 uri 不是绝对的,对此有什么想法吗?
    • @John 通常这意味着你只有一个相对的 /path 而不是像localhost:8080/path这样的完整 uri
    • 我发现哪里错了,我把它作为其他可能遇到同样问题的人的答案,这是一个愚蠢的错误,非常感谢你的帮助@DCTID
    【解决方案2】:

    好的,这很愚蠢,我想我最好只回答我自己的问题,以便处于相同或类似情况的人可以获得一些帮助,我解决问题的方法是我应该将 InfoBook 类中的 restTemplate 移到方法之外并给出它是一种获取和设置方法,在我的测试中,我应该只设置我的模拟 restTemplate 然后一切都会被正确模拟,所以我不使用真正的 restTemplate,我应该使用我的模拟,这就是为什么它们不同而我的模拟数据未返回。

    感谢所有帮助过的人。

    【讨论】:

      猜你喜欢
      • 2018-10-20
      • 1970-01-01
      • 1970-01-01
      • 2018-02-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-31
      • 2011-08-06
      • 1970-01-01
      相关资源
      最近更新 更多