转载: https://cloud.tencent.com/developer/article/1756319
最近有个开发同学过来求助说某个系统接受的时候,发现里面的代码几乎没有单元测试,只是对几个DTO做了set/get的测试!看能不能帮忙指导下怎么开展。代码pull下来看了看,写了个demo,顺便解决了两个Mock方面的问题,提交上去供开发同学继续写用例。
问题1:static block 静态代码块
这是第一个遇到的问题。笔者按照一般套路做好Mockito的三板斧之后开始跑用例,结果发现执行失败。
一排查结果发现,这服务还使用了JNI。
@Service
@Transactional
public class BookServiceImpl implements BookService {
static {
System.out.println("static block called");
System.loadLibrary("libOnlyRunOnLinux.so");
}
抛以下的错误
java.lang.UnsatisfiedLinkError: no libOnlyRunOnLinux.so in java.library.path:
这是一个连接JNI和java的adapter服务。因为开发测试是在Windows上码代码,而上游开发只提供了so包,然后就跑挂了。
一开始,建议开发同学可否提供根据runtime来动态加载so或者dll。咨询一番后被告知,这个so包是另外一个team提供的,短期内改不了。
那就退而求其次吧,是不是可以避免使用静态块,而是使用类似@PostConstruct的方式来提供。
@PostConstruct
loadJNI(){
System.out.println("static block called");
System.loadLibrary("libOnlyRunOnLinux.so");
}
结果又被告知,之前试过,貌似会导致core,所以才使用的静态块。
于是,问题就变成了如何来绕过这个so包的导入,反正单测的时候哦这个服务是要被mock掉的。
关于静态的东西,Mockito就搞不定了,得请出Powermock了。
@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("com.xpinjection.springboot.service.BookServiceImpl")
public class BookServiceImplTest
通过这个@SuppressStaticInitializationFor来如何阻止静态代码块的执行呢。
当然引入Powermock的时候,要注意和使用的Mockito的版本匹配问题。
第一个问题搞定。
问题2:@Autowired Gson
在一个服务类中使用了gson,不过是通过@Autowired方式依赖注入的。
@Autowired
Gson gson ;
然后又在方法中使用了。请忽略,随便找了一个网上的demo, 加的这两行代码。
@Override
public List<Book> addBooks(Map<String, String> books) {
gson.toJson(books);
return books.entrySet().stream()
.map(entry -> new Book(entry.getKey(), entry.getValue()))
.map(bookDao::save)
.collect(toList());
}
这个时候就会遇到一个两难的问题。假设不对Gson进行mock, 以下的用例就会抛空指针
public class BookServiceImplTest {
@Mock
private BookDao dao;
private BookService bookService;
@Before
public void init() {
bookService = new BookServiceImpl(dao);
}
@Test
public void ifNoBooksPassedEmptyListIsReturned() {
assertThat(bookService.addBooks(Collections.emptyMap()), is(empty()));
}
因为Gson的实例化是Spring托管的
java.lang.NullPointerException
at com.xpinjection.springboot.service.BookServiceImpl.addBooks(BookServiceImpl.java:37)
如果在测试用例中添加一个
@Mock
Gson gson ;
又需要我们给出各种stub。由于Gson在这里是个底层方法,不是应用的其它服务,理论上不应该被mock,就像我们不太会去mock StringUtil类似的。
那怎么解决呢?
先试试
@Spy
Gson gson ;
然后再造一个测试桩
when(gson.toJson(any())).thenCallRealMethod();
思路是说,当使用json的时候,需要使用真实的,而不是mock的实例。然而,
org.mockito.exceptions.base.MockitoException: Unable to initialize @Spy annotated field 'gson'.
Cannot mock/spy class com.google.gson.Gson
Mockito cannot mock/spy following:
- final classes
- anonymous classes
- primitive types
因为Gson是一个final classes,因此不能被mock。
那咋办呢?还是得拿出Spring测试框架提供的依赖注入工具
org.springframework.test.util.ReflectionTestUtils
Gson gson = new Gson();
private BookService bookService;
@Before
public void init() {
bookService = new BookServiceImpl(dao);
ReflectionTestUtils.setField(bookService, "gson", gson);
}
首先,还是正常来new 一个Gson实例,然后通过ReflectionTestUtils将这个实例注入到bookService的私有变量gson。这样,后续的gson在调用时就可以获取到一个可以正常使用的gson了。
小绿条又回来了。