Jmockit使用详解之Faking

Jmockit使用详解之Faking

简介

Faking主要用来构造假的实现,通常是部分伪造方法和构造函数,来到得到期望的运行逻辑,常用来伪造第三方组件(发邮件,发请求等)。

伪造方法和类

方法通过@Mock来伪造,被伪造的类只要继承mockit.MockUp< T >就行,这里T是需要被伪造的类

public final class FakeLoginContext extends MockUp<LoginContext>
{
   //伪造了LoginContext的部分方法和构造函数

   //伪造了构造函数
   @Mock
   public void $init(String name, CallbackHandler callback) {
      assertEquals("test", name);
      assertNotNull(callback);
   }
   @Mock
   public void login() {}

   @Mock
   public Subject getSubject() { return null; }
}

当一个类的方法被伪造后,在实际调用过程中,真实类的这些方法将会被替换成伪造类的方法。

注意@Mock标注的方法要和实际该类(或父类,不包括Object类)中的方法签名匹配:对于普通方法,需要名称和参数一致,对于构造函数,只需要构造函数参数一致,同时名字用”$init”。需要注意的是,构造函数的mock实际是要基于原有构造函数能够成功运行,init的方法中的内容只不过在原有构造函数后构造后多加了一些内容,因此常配合Invocation来使用,这样可以获取构造后的实例,并做一些改动。

如果@Mock的方法在实际类中找不到,将会抛IllegalArgumentException异常。

使用伪造类

伪造类可以作用于整个test类,或者一个test方法,@BeforeClass,@BeforeMethod,@Before,@BeforeEach也可以使用

@Test
public void applyingAFakeClass() throws Exception {
   //直接新建了整个fake类,它就可以作用整个方法
   new FakeLoginContext());

   // Inside an application class which creates a suitable CallbackHandler:
   new LoginContext("test", callbackHandler).login();

   ...
}

Faking的伪造能力

private,protect,后者包private的类,静态方法,final方法,native方法都可以伪造。甚至 一个静态方法能够被实例方法伪造(伪造类中定义的非静态方法和静态放的签名相同)。需要注意的是抽象方法不能够被伪造。

伪造不确定的类

public interface Service { int doSomething(); }
final class ServiceImpl implements Service { public int doSomething() { return 1; } }

public final class TestedUnit
{
   //类实现了接口
   private final Service service1 = new ServiceImpl();
   //这里使用了匿名内部类,是无法访问的
   private final Service service2 = new Service() { public int doSomething() { return 2; } };

   public int businessOperation() {
      return service1.doSomething() + service2.doSomething();
   }
}

给定一个基类,我们可以伪造其所有的实现类(不仅是已经存在,也包括后序创建的)

public <T extends Service> void fakingImplementationClassesFromAGivenBaseType() {
   new MockUp<T>() {
      @Mock int doSomething() { return 7; }
   };

   int result = new TestedUnit().businessOperation();

   assertEquals(14, result);
}

伪造类初始化

下面的方法可以伪造静态初始化代码块,也包括静态属性:

注意:运行时才有效的静态属性的赋值将会无效(jvm只初始化一次类)

@Test
public void fakingStaticInitializers() {
   new MockUp<ClassWithStaticInitializers>() {
      @Mock
      void $clinit() {
         // Do nothing here (usually).
      }
   };

   ClassWithStaticInitializers.doSomething();
}

访问调用上下文:

伪造的方法可以有一个多余的Invocation参数,并且作为第一个参数,这个参数在调用的时候将会自动传入。调用的上下文将可以通过该方法获取。比如getInvokedInstance()将会访返回伪造的实例,如果返回null,表明这个方法是静态的。

@Test
public void accessingTheFakedInstanceInFakeMethods() throws Exception
{
   Subject testSubject = new Subject();

   new MockUp<LoginContext>() {
      @Mock
      void $init(Invocation invocation, String name, Subject subject) {
         assertNotNull(name);
         assertSame(testSubject, subject);

         // Gets the invoked instance.
         LoginContext loginContext = invocation.getInvokedInstance();

         // Verifies that this is the first invocation.
         // 获取伪造类的调用次数
         assertEquals(1, invocation.getInvocationCount());

         // Forces setting of private Subject field, since no setter is available.
         Deencapsulation.setField(loginContext, subject);
      }

      @Mock
      void login(Invocation invocation) {
         // Gets the invoked instance.
         LoginContext loginContext = invocation.getInvokedInstance();

         // getSubject() returns null until the subject is authenticated.
         assertNull(loginContext.getSubject());

         // Private field set to true when login succeeds.
         Deencapsulation.setField(loginContext, "loginSucceeded", true);
      }

      @Mock
      void logout(Invocation invocation) {
         // Gets the invoked instance.
         LoginContext loginContext = invocation.getInvokedInstance();

         assertSame(testSubject, loginContext.getSubject());
      }
   };

   LoginContext theFakedInstance = new LoginContext("test", testSubject);
   theFakedInstance.login();
   theFakedInstance.logout();
}

伪造类中继续调用真实类的方法

@Test
public void proceedIntoRealImplementationsOfFakedMethods() throws Exception {
   // Create objects used by the code under test:
   LoginContext loginContext = new LoginContext("test", null, null, configuration);

   // Apply fakes:
   ProceedingFakeLoginContext fakeInstance = new ProceedingFakeLoginContext();

   // Exercise the code under test:
   assertNull(loginContext.getSubject());
   loginContext.login();
   assertNotNull(loginContext.getSubject());
   assertTrue(fakeInstance.loggedIn);

   fakeInstance.ignoreLogout = true;
   loginContext.logout(); // first entry: do nothing
   assertTrue(fakeInstance.loggedIn);

   fakeInstance.ignoreLogout = false;
   loginContext.logout(); // second entry: execute real implementation
   assertFalse(fakeInstance.loggedIn);
}

static final class ProceedingFakeLoginContext extends MockUp<LoginContext>
{
   boolean ignoreLogout;
   boolean loggedIn;

   @Mock
   void login(Invocation inv) throws LoginException {
      try {
         //执行真实的方法
         inv.proceed(); // executes the real code of the faked method
         loggedIn = true;
      }
      finally {
         // This is here just to show that arbitrary actions can be taken inside the
         // fake, before and/or after the real method gets executed.
         LoginContext lc = inv.getInvokedInstance();
         System.out.println("Login attempted for " + lc.getSubject());
      }
   }

   @Mock
   void logout(Invocation inv) throws LoginException {
      // We can choose to proceed into the real implementation or not.
      if (!ignoreLogout) {
         inv.proceed();
         loggedIn = false;
      }
   }
}

伪造的重复使用

在Junit中可以使会用@Before注解

public class MyTestClass
{
   @BeforeClass
   public static void applySharedFakes() {
      new MockUp<LoginContext>() {
         // shared @Mock's here...
      };
   }

   // test methods that will share the fakes applied above...
}

全局伪造

使用JUnit的Suite方法,在MyFirstTest.class, MySecondTest.class将会有效

@RunWith(Suite.class)
@Suite.SuiteClasses({MyFirstTest.class, MySecondTest.class})
public final class TestSuite
{
   @BeforeClass
   public static void applyGlobalFake() {
      new FakeLogging();
   }
}

添加AOP的advice

public final class MethodTiming extends MockUp<Object>
{
   private final Map<Method, Long> methodTimes = new HashMap<>();

   public MethodTiming(Class<?> targetClass) { super(targetClass); }
   MethodTiming(String className) throws ClassNotFoundException { super(Class.forName(className)); }
   //这里并不是Mock,是AOP的advice,写法是固定的
   //用来统计所有方法的调用时间
   @Mock
   public Object $advice(Invocation invocation) {
      long timeBefore = System.nanoTime();

      try {
         return invocation.proceed();
      }
      finally {
         long timeAfter = System.nanoTime();
         long dt = timeAfter - timeBefore;

         Method executedMethod = invocation.getInvokedMember();
         Long dtUntilLastExecution = methodTimes.get(executedMethod);
         Long dtUntilNow = dtUntilLastExecution == null ? dt : dtUntilLastExecution + dt;
         methodTimes.put(executedMethod, dtUntilNow);
      }
   }

   @Override
   protected void onTearDown() {
      System.out.println("\nTotal timings for methods in " + targetType + " (ms)");

      for (Entry<Method, Long> methodAndTime : methodTimes.entrySet()) {
         Method method = methodAndTime.getKey();
         long dtNanos = methodAndTime.getValue();
         long dtMillis = dtNanos / 1000000L;
         System.out.println("\t" + method + " = " + dtMillis);
      }
   }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值