1. @Injectable 与 @Mocked的不同

  2. //@Mocked与@Injectable的不同
    public class MockedAndInjectable {
    
    	@Test
    	public void testMocked(@Mocked Locale locale) {
    		// 静态方法不起作用了,返回了null
    		Assert.assertTrue(Locale.getDefault() == null);
    		// 非静态方法(返回类型为String)也不起作用了,返回了null
    		Assert.assertTrue(locale.getCountry() == null);
    		// 自已new一个,也同样如此,方法都被mock了
    		Locale chinaLocale = new Locale("zh", "CN");
    		Assert.assertTrue(chinaLocale.getCountry() == null);
    	}
    
    	@Test
    	public void testInjectable(@Injectable Locale locale) {
    		// 静态方法不mock
    		Assert.assertTrue(Locale.getDefault() != null);
    		// 非静态方法(返回类型为String)也不起作用了,返回了null,但仅仅限于locale这个对象
    		Assert.assertTrue(locale.getCountry() == null);
    		// 自已new一个,并不受影响
    		Locale chinaLocale = new Locale("zh", "CN");
    		Assert.assertTrue(chinaLocale.getCountry().equals("CN"));
    	}
    }


    @Injectable 也是告诉 JMockit生成一个Mocked对象,但@Injectable只是针对其修饰的实例,而@Mocked是针对其修饰类的所有实例。
    此外,@Injectable对类的静态方法,构造函数没有影响。因为它只影响某一个实例嘛!

  3. @Tested & @Injectable 两个好基友,通常搭配使用

    为便于演示,我们以电商网站下订单的场景为例:在买家下订单时,电商网站后台程序需要校验买家的身份(是否合法,例如是否在黑名单中),
    若下订单没有问题还要发邮件给买家。    相信下面的代码,你一定能看明白 。

  4. // 邮件服务类,用于发邮件
    public interface MailService {
    
    	/**
    	 * 发送邮件
    	 * 
    	 * @param userId
    	 *            邮件接受人id
    	 * @param content
    	 *            邮件内容
    	 * @return 发送成功了,就返回true,否则返回false
    	 */
    	public boolean sendMail(long userId, String content);
    }
    // 用户身份校验  
    public interface UserCheckService {
    
    	/**
    	 * 校验某个用户是否是合法用户
    	 * 
    	 * @param userId
    	 *            用户ID
    	 * @return 合法的就返回true,否则返回false 
    	 */
    	public boolean check(long userId);
    }//订单服务类 ,用于下订单
    public class OrderService {
    	// 邮件服务类,用于向某用户发邮件。
    	MailService mailService;
    	// 用户身份校验类,用于校验某个用户是不是合法用户
    	@Resource
    	UserCheckService userCheckService;
    
    	// 构造函数
    	public OrderService(MailService mailService) {
    		this.mailService = mailService;
    	}
    
    	/**
    	 * 下订单
    	 * 
    	 * @param buyerId
    	 *            买家ID
    	 * @param itemId
    	 *            商品id
    	 * @return 返回 下订单是否成功
    	 */
    	public boolean submitOrder(long buyerId, long itemId) {
    		// 先校验用户身份
    		if (!userCheckService.check(buyerId)) {
    			// 用户身份不合法
    			return false;
    		}
    		// 下单逻辑代码,
    		// 省略...
    		// 下单完成,给买家发邮件
    		if (!this.mailService.sendMail(buyerId, "下单成功")) {
    			// 邮件发送成功
    			return false;
    		}
    		return true;
    	}
    }

    假设现在我们需要测试OrderService类的submitOrder方法,可是OrderService依赖MailService,UserCheckService类,
    在测试过程中,我们并不想真正连结邮件服务器,也不想连结校验用户身份的服务器校验用户身份,怎么办呢?
    此时@Tested与@Injectable就排上用场了!请看下面的测试程序:

        //@Tested与@Injectable搭配使用
    public class TestedAndInjectable {
       //@Tested修饰的类,表示是我们要测试对象,在这里表示,我想测试订单服务类。JMockit也会帮我们实例化这个测试对象
       @Tested
       OrderService orderService;
       //测试用户ID
       long testUserId = 123456l;
       //测试商品id
       long testItemId = 456789l;
    
       // 测试注入方式
       @Test
       public void testSubmitOrder(@Injectable MailService mailService, 
         @Injectable UserCheckService userCheckService) {
    	new Expectations() {
    	   {
    		 // 当向testUserId发邮件时,假设都发成功了
    		 mailService.sendMail(testUserId, anyString);
    		 result = true;
    		// 当检验testUserId的身份时,假设该用户都是合法的
    		 userCheckService.check(testUserId);
    		result = true;
    	     }
             };
    	// JMockit帮我们实例化了mailService了,并通过OrderService的构造函数,注入到orderService对象中。 
    	//JMockit帮我们实例化了userCheckService了,并通过OrderService的属性,注入到orderService对象中。 
    	Assert.assertTrue(orderService.submitOrder(testUserId, testItemId));
        }
    }
  5. @Tested & @Injectable功能总结

    @Injectable 也表示一个Mocked对象,相比@Mocked,只不过只影响类的一个实例。而@Mocked默认是影响类的所有实例。
    @Tested表示被测试对象。如果该对象没有赋值,JMockit会去实例化它,若@Tested的构造函数有参数,
    则JMockit通过在测试属性&测试参数中查找@Injectable修饰的Mocked对象注入@Tested对象的构造函数来实例化,
    不然,则用无参构造函数来实例化。除了构造函数的注入,JMockit还会通过属性查找的方式,把@Injectable对象注入到@Tested对象中。
     注入的匹配规则:先类型,再名称(构造函数参数名,类的属性名)。若找到多个可以注入的@Injectable,则选择最优先定义的@Injectable对象。
    当然,我们的测试程序要尽量避免这种情况出现。因为给哪个测试属性/测试参数加@Injectable,是人为控制的。

  6. 什么测试场景,我们要使用@Tested & @Injectable 

     显然,当我们需要手工管理被测试类的依赖时,就需要用到@Tested & @Injectable。
     两者搭配起来用,JMockit就能帮我们轻松搞定被测试类及其依赖注入细节。