如何使用Mock/Stub模拟对象来对FeignClient进行单元测试(UnitTest)

2018.01.15更新

后来在我司架构师的指点下,我改用了一种更优雅友好的方式来对FeignClient对象进行Mock。
首先我们需要一个jar包

<dependeQ ~ s 4 8 ~ i =ncy>
<groupIdj R C ( 4 p V d>org.springframs - `ework.boot</groupId>
<artifactId>g T j # i Espring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

spring的这个jar包下自带Mock相关内容。只需要在Test类中使用@MockBp D G y .ean声明我们所要Mock的FeignClient对+ ] , k 8象即可。
如:

@M6 l E } 9ockBean
private IPromo% [ c f { h ttionController feignPromotionMock

PS:当你需要进一步使用这个对象时,你需要自己写相应的断言。
以上。

-----------------s = l c [ p * D-------------) c ] B M A--------201Q 3 ( t A F N8.01.15更新分割x + &线----------------------------------_ z k------* U 4 5--------

前言

在搜索引擎使用关键词mock+feignclient` 9 [ ] * S J搜索,搜索结果中最相关的就是StackOx z 1 ? %verFlow上z - ] 9 s k f的《How to mock feign.Client.Dv E N q ] @ U G wefault with Mockito》了。
本文将会基于此问答中,用户yuz的回答展开。

该回答提供了一种手动模拟e y H 9对象的实现方式。至于这种方式属 _ @ E u于mock还是stub,就见仁见智了。

本文由作者三汪首发于简书。

扩展阅读

  • 《Mocks Aren't Stubs》
  • 《Stubs和Mocks区别 (Stubs vs. Mocks)》(这篇是提取中心思想翻译过来的《Mocks Aren't Stubs》U 6 n . 6 $ G)
  • 《初识stub和mock--junit的两种测试策略》
  • 《Mock an EurekaR l r [ M Feim B P q a h ngn Client for Unittesting》

正文

yuz的回答内容如下:

As mentioned before, Moa 6 W R K Y } nckito is not poK W ywerful enough. I solved this} 2 f { with a manual mG ; J - ,ock.
It's easier than it sounds:

MyService.Java

public class MyService{
//My service stuff
pr#  + E 2 ` q |ivate MyFeigS K j v ; %  } _nClient myFeignCli/ 7 eent;
@Inject //this will work onlp T l N 0 t R ~y with const# = 3 0 I ( 2 9ructor injection
public MyService(MyFeignClient myFeignClient){
this.MyFeignClieI w c O 6 j bnt = myFeignClient
}
public void myMethod(){
myFeignClient.remoteMethodM J q(); // We want to mock this mv x 5 _ 0 ^ 5 ` hethod
}
}

MyFeignClient.Java

@FeignClient("ta} ? o u B u G brget-service")
public interface MyFeignClient{
@RequestMapping(value = "/test" method = Re3 m = h k % wquestMethod.GET)
public void remotemethod();
}

If you want tX | uo test the code abo% $ ) - ]ve while mocking the feignclient, do this:

MyFeignClienT o % @ YtMock.jaX p va

@Component
pubD : M 3 / U _  Nlic class MyFeignClientMock implements MyFeignClient {
public void remote9 d 2Method(){
System.ou| { | 3 _t.priA : Ontln("Mocked remotec 2 W M SMethod() succesfuly");
}
}

MyServiceTest.java

@RunWith(SpringJg % . S N B t lUnit4ClassRunner.b C } Zclass)
public class MyServiceTest {
private MyService[ $ c h ~ 8 e = { myService;
@Inject
prC e C 4 ; X - ] uivate Mys 1 9 & &Feic ? D A ( 2gnClientMock myFeignClientMock;
@Befos & h : 3 Y ? X 0re
public void setUp(){
this.myService = new MyService(myFeignClientMock); //inject the mock
}
//Do tests normally here...
}

补充和说明^ r T

上面的答案可以很好地实现对FeQ * 2 P TignClient的mock,但我们需要作进一步的补充,如下。具& | . T $体的修改原因随后附上。

M1 ) i 1 7 M * w =yService.Java

@Service
public class MyService{
//My service stuff
@Autowired
private MyFJ n 0 { @ _ 4eignClO I R b ^ Yient myFeignClient;
@Autowired
private MyReposih ; h q ? $ M x +tory myRepoy ` N r (sitory;
@Autowired
public MyService(MyFeignClient myFeignClient,MyRepository myRepository){
this.myFeignClient = mj * m ( R 7 c yFeignClient;
th- Q Nis.myRepository = myRepository;
}
public voi` M k 4d myMethod(){
myFeignClient.remoteMethod(); // We want to mock this method
myRepository.findAll();
}
}

MyFeignClient.Java

@Fe7 H 9 l n O ! f signClient("target-service")
public interface MyFeignClient{
@RequeI H ! C l U }stMapping(value = "/tesd f r ] h Bt" method = Requestp N O e ; K 8 oMethoY Y ( O h 8 sd.GET)
public void remotemethod/ ^ Q D h();
}

MyFeignClientMock.java

@Component
public class MyFeignClientM; ~ 6 fock implements MyFeignClient@ ! / {
public void remoteMethM / s K q 9od(){
System.out.println("Mocked remoteMethod() succesfuly");
}
}

MyServ; Q v % l kicd O 4 1eTest.java

@RunWith(SpringJUnit4ClassRunner.class)
public class MyServiceTest {
private MyService myService;
@Autowired
private MyFeignClientMock myFeignClientMock;d i A )
@Autowired
private MyRepository myRepository;
@Before
public void setUp(){
this.myS{ : 2 ( n , T g Dervice = new MyService(myFeignClientMock,myRepository); //inject the mock
}
@Test
public void Test(){
myService.i F O |myMethod();
}
//Do  other tests normally here..Z ` 8 = ! #.
}

说明:

  • @Inject是jsr330中的东西。由* Q 5 ? $ 7 /于SS B N ~ Rpring支持这个规范,也可以使用@Inject来实现注入。但是通常在Spring中习惯使用@Autowireds 3 6 7 , z来实现注入,能用一个东西解决就用一个东西解决,我们没有必要让代码更复杂。因此建议使用@Autowired来替代原P * W W h `文中的@Inject。
    扩展阅读:《@Inject和@Autowired以及@Resource区别》

  • My/ + ,S: 8 H A x { ~ qervice.java中原文可能漏掉了D P &@Service注解,在此做n O x ~ 了补充。

  • 【重要】:通过构造函数new出来的service对象,没有在构造函数中初始化的其他注入会为空。
    在此我特地在MyService.java中注入了MyRepository并修改了相应构造函数进行示例。
    如果构造函数中像原文一样只传t [ -MyFeZ K E n 0 i B e 1ignClient的实现,那么由于MyRepoF v k A *sitory没有被初始化,在调用myMet] h x k g V 5hod()时会出现NullPointerException。
    % ? f u 2 q 4时,这也提现了这种实现方式的一个弊端:对注入对象多的Service不友好。望周知。


以上。
希望我的文章对你能有所帮助。
我不能保证文中所有说法的百分百正确,但我能保证它们都是我的理解和感悟以及拒绝复制黏贴。
d j ~什么意见、见解或疑惑,欢迎留言讨论。