mockito大家都比较熟悉了,存在或者不存在,都不要紧,mockito让你有一种只要一出手,就知道有没有的感觉。但是它也不是万能的,比如静态方法、私有方法,它就无能为力了。这是为什么呢?当然不是mockito的框架或现有技术解决不了,而是出于某些原因或立场,比如测试理念观点。甚至在mockito的FAQ中,作者明确了每一项未实现的功能不支持的原因,或者干脆说已经有别的工具实现了,需要的话,去用那个工具吧,我不愿意重复造轮子。
当然实现这些也并非轻而意举,比如如何mock final类,特别是jdk中的final类,比如String。但作为系统类,在任何时候都不应该可以被修改(即使是有办法修改,也不建议去修改,也没有必要修改,否则重新设计一门新语言即可),特别是对于java.lang包下的类,如基本的数据类型Integer、Long等。java agent可以修改由AppClassLoader加载的类,而endorsed技术也只允许覆盖在有限的限制列表中的类。而powermock采取的方案是,如果需要mock的是系统类的final方法和静态方法,PowerMock不会直接修改系统类的class文件,而是修改调用系统类的class文件,以满足mock需求。
mockito的实现原理是用asm给需要mock的对象生成对应的代理对象,然后使用mock出来的对象即可。而在spring框架中,SpyBean与MockBean的原理也是一样,只不过还需要多做一步,就是用mock后的对象替换容器中原有的对象。
尽管如此,但mockito仍然有一些限制,罗列及翻译如下:
- Mockito 2.x specific limitations
- Mockito 2.x限制
-
-
- Requires Java 6+
- 需JDK 6以上版本
-
-
- Cannot mock static methods
- 不支持mock静态方法
-
-
- Cannot mock constructors
- 不支持mock构造函数
-
-
- Cannot mock equals(), hashCode(). Firstly, you should not mock those methods. Secondly, Mockito defines and depends upon a specific implementation of these methods. Redefining them might break Mockito.
- Mocking is only possible on VMs that are supported by Objenesis. Don't worry, most VMs should work just fine.
- 不支持mock equals与hashCode方法。mockito认为不应该mock这两个方法,因为mockito的实现方案依赖于这两个方法。重新定义这两个方法可以会导致mockito异常。同时mocking只能在被Objenesis支持的vm上运行,目前在大部分vm上都能运行得很好。(Objenesis是一个使用旁门左道创建类实例的库,除了调用类的构造函数)
- Spying on real methods where real implementation references outer Class via OuterClass.this is impossible. Don't worry, this is extremely rare case.
- 不支持当内部类的某个方法实现中引用外部OuterClass.this的情形。但不需要担心,这样的例子真的很少见。
- Can I mock static methods?
- 支持mock静态方法吗
-
-
- No. Mockito prefers object orientation and dependency injection over static, procedural code that is hard to understand & change. If you deal with scary legacy code you can use JMockit or Powermock to mock static methods.
- 不支持,Mockito更倾向于在面向对象与依赖注入的层面上mock,而不是mock静态方法,静态方法这种面向过程的代码比较难理解与改变。如果你要处理这些恐怖的遗留代码,那么请使用JMockit或者Powermock来mock静态方法。
- Can I mock private methods?
- 支持mock私有方法吗
-
-
- No. From the standpoint of testing... private methods don't exist. More about private methods here.
- 不支持。从测试方法的观点来看,其实私有方法是不存在的(需要测试的都是公开的方法)。更多关于私有方法的观点讲参照这里(注:原文这里是个链接)。
-
-
- Why Mockito doesn't mock private methods?
- 为什么Mockito不支持mock私有方法
-
-
- Firstly, we are not dogmatic about mocking private methods. We just don't care about private methods because from the standpoint of testing, private methods don't exist. Here are a couple of reasons Mockito doesn't mock private methods:
- 首先,关于mock私有方法论断,不是我们自以为是。我们不关注私有方法是因为测试方法相关的观点,私有方法是不存在的。这里有一些关于Mockito不支持mock私有方法的原因:
-
-
- 1. It requires hacking of classloaders that is never bullet proof and it changes the API (you must use custom test runner, annotate the class, etc.).
- mock私有方法需要侵入classloader,虽然classloader并非刀枪不入,但是这样会改变你使用Mockito API的方式(比如使用自定义的test runner,使用特殊的注解等)
-
- 翻译加注:因为私有方法不可见,无法使用原有的mockito语法实现mock,所以必然需要其他途径来达到目的,而powermock使用自定义的classloader与runner来实现。静态方法的mock也是同样的原因,因为没有对象可以被mock,只能通过classloader做文章,自定义的classloader想返回什么字节码就返回什么字节码。
-
-
- 2.It is very easy to work around - just change the visibility of method from private to package-protected (or protected).
- 其实mock私有方法可以很简易地解决,只需要改变方法的可见性,如将private改为default或protected。
-
-
- 3. It requires the team to spend time implementing & maintaining it. And it does not make sense given point (2) and a fact that it is already implemented in different tool (powermock).
- 它需要Mockito团队花时间去实现并且维护它,但是基于观点2这又讲不过去(即说不服自己),并且事实上已经有不同的工具(powermock)实现它了。
-
-
- 4. Finally... Mocking private methods is a hint that there is something wrong with Object Oriented understanding. In OO you want objects (or roles) to collaborate, not methods. Forget about pascal & procedural code. Think in objects.
- 最后,对私有方法进行mock意味着对面向对象编程的概念理解有偏差。在面向对象的理念中你需要的是对象(或角色)来协作,而不是方法。忘记pascal,忘记面向过程编程吧,以面向对象的方式来思考。
- public class MyUtil {
- public static String hello() {
- return "hello";
- }
- }
-
- public class Person {
- private String name;
-
- public Person(String name) {
- this.name = name;
- }
-
- private String getName2() {
- return this.name;
- }
-
- public String getName() {
- return getName2();
- }
- }
- package com.kidshelloworld.test;
-
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
-
- @SpringBootApplication
- public class TestApplication {
- public static void main(String[] args) {
- SpringApplication.run(TestApplication.class, args);
- }
-
- }
- package com.kidshelloworld.test;
-
- import org.junit.Assert;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.mockito.Mockito;
- import org.powermock.api.mockito.PowerMockito;
- import org.powermock.core.classloader.annotations.PowerMockIgnore;
- import org.powermock.core.classloader.annotations.PrepareForTest;
- import org.powermock.modules.junit4.PowerMockRunner;
- import org.powermock.modules.junit4.PowerMockRunnerDelegate;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.test.context.SpringBootTest;
- import org.springframework.boot.test.mock.mockito.MockBean;
- import org.springframework.boot.test.mock.mockito.SpyBean;
- import org.springframework.test.context.ActiveProfiles;
- import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-
- @PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
- @RunWith(PowerMockRunner.class)
- @PowerMockIgnore(value = { "javax.management.*", "javax.net.ssl.*", "javax.net.SocketFactory" })
- @ActiveProfiles(value = { "testcase" })
- @PrepareForTest(value = { MyUtil.class, Person.class })
- @SpringBootTest(classes = TestApplication.class)
- public class TestMyUtil {
- private Person spyPerson;
-
- @autowired
- public void prepare() {
- PowerMockito.mockStatic(MyUtil.class);
- Mockito.when(MyUtil.hello()).thenReturn("hi");
-
- spyPerson = PowerMockito.spy(new Person("xyz");
- }
-
- @Test
- public void testHello() {
- Assert.assertEquals("hi", MyUtil.hello());
- }
-
- @Test
- public void testGetName() {
- PowerMockito.when(spy, "getName2").thenReturn("abc");
- String name = spy.getName();
- Assert.assertEquals("abc", name);
- }
- }
powermock使用了自定义的classloader来解决mock静态方法与私有方法的问题,因此其会为加了PrepareForTest注解的类生成对应的classloader来加载用到的类,这样就可能会导致其与系统的classloader加载了相同的类,导致类型转换失败,PowerMockIgnore注解则是告诉powermock放弃加载指定的这些类。
同时powermock使用了自定义的PowerMockRunner,与spring集成时,可以代理至SpringJUnit4ClassRunner。