<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; Mock</title>
	<atom:link href="https://blog.gmem.cc/tag/mock/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Fri, 03 Apr 2026 04:13:36 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>基于Spring Test和Mockito进行单元测试</title>
		<link>https://blog.gmem.cc/ut-with-spring-and-mockito</link>
		<comments>https://blog.gmem.cc/ut-with-spring-and-mockito#comments</comments>
		<pubDate>Tue, 08 Sep 2015 06:41:43 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Test]]></category>
		<category><![CDATA[Mock]]></category>
		<category><![CDATA[SpringMVC]]></category>
		<category><![CDATA[单元测试]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=8180</guid>
		<description><![CDATA[<p>场景说明 本文引入一个简单的银行业务场景，用来阐述如何集成Spring Test、Junit、Mockito，以简化单元测试工作。该场景主要的业务代码如下： [crayon-69d314daf1d23919218922/] 假设你已经实现了服务AccountService： [crayon-69d314daf1d28137353661/] 而你的搭档负责的PersonService还没有开发完毕，如何方便的进行单元测试呢？ 在JUnit中集成Spring上下文的支持 你可能会觉得，我们不需要在单元测试中引入Spring。对于上面的例子的确可以这么说，它太简单了，AccountServiceImpl 依赖的PersonService完全可以通过setter手工注入。但是实际的开发场景要比这个例子复杂的多，待测试类可能和Spring管理的Beans存在很多关联，它可能依赖于Spring提供的数据源、事务管理器，等等。这些Bean如果都手工管理，将是相当繁琐无味的工作。 使用JUnit 4.x提供的注解[crayon-69d314daf1d2c312988367-i/] ，可以指定单元测试的“运行类”，运行类必须继承自[crayon-69d314daf1d2e077747186-i/] 并实现[crayon-69d314daf1d30150599002-i/] 方法。Spring Test框架提供的运行类是[crayon-69d314daf1d32233297406-i/] ，使用该类可以轻松的将Spring和JUnit进行集成。该类的用法示例如下： [crayon-69d314daf1d34071060090/] AccountService的测试用例 依据上一节的知识，我们编写集成Spring Test的测试用例： [crayon-69d314daf1d36237414738/] 基于Mockito进行仿冒 引入Spring后，运行单元测试AccountServiceTest，会得到一个NoSuchBeanDefinitionException，这是因为AccountServiceImpl依赖的PersonService没有在Spring中注册。前面我们提到过，PersonService由搭档开发且尚未完成，这个时候要想单独测试AccountServiceImpl，那么就需要开发一个模拟的PersonService。最直接的模拟就是实现PersonService接口，但是不方便、工作量大，因此我们引入Mock框架：Mockito。 本文不去讨论Mockito的API细节，有兴趣的同学可以参考：使用Mockito进行单元测试 可以参考如下方式，单独将Mocketo和JUnit集成： <a class="read-more" href="https://blog.gmem.cc/ut-with-spring-and-mockito">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/ut-with-spring-and-mockito">基于Spring Test和Mockito进行单元测试</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">场景说明</span></div>
<p>本文引入一个简单的银行业务场景，用来阐述如何集成Spring Test、Junit、Mockito，以简化单元测试工作。该场景主要的业务代码如下：</p>
<pre class="crayon-plain-tag">/**
 * 人员
 */
public class Person
{
    private int     id;
    private String  name;
    private Account defaultAccount;
}
/**
 * 账户
 */
public class Account
{
    private int  id;
    private int  balance;
    private Person person;
}
/**
 * 人员服务接口
 *
 */
public interface PersonService
{
    /**
     * 查询人员用户
     */
    Person getPerson( int id );
    /**
    * 得到人员默认账户
    */
    Account getDefaultAccount( Person p );
}
/**
 * 账户服务接口
 *
 */
public interface AccountService
{
    /**
     * 查询人员默认账户余额
     */
    int queryBalanceOfDefaultAccount( int personId );
}</pre>
<p>假设你已经实现了服务AccountService：</p>
<pre class="crayon-plain-tag">@Service ( "accountService" )
public class AccountServiceImpl implements AccountService
{
    private Map&lt;Integer, Object[]&gt; accountDatabase;

    @Inject
    private PersonService          personService;

    @PostConstruct
    public void init()
    {
        accountDatabase = new HashMap&lt;Integer, Object[]&gt;();
        //字段：账号,余额
        accountDatabase.put( 100, new Object[] { "6225100", 68861 } );
        accountDatabase.put( 101, new Object[] { "6225101", 1851 } );
        accountDatabase.put( 102, new Object[] { "6225102", 845 } );
        accountDatabase.put( 103, new Object[] { "6225103", 16598 } );
    }

    @Override
    public int queryBalanceOfDefaultAccount( int personId )
    {
        Person person = personService.getPerson( personId );
        Account defaultAccount = person.getDefaultAccount();
        return (Integer) accountDatabase.get( defaultAccount.getId() )[1];
    }

}</pre>
<p>而你的搭档负责的PersonService还没有开发完毕，如何方便的进行单元测试呢？</p>
<div class="blog_h2"><span class="graybg">在JUnit中集成Spring上下文的支持</span></div>
<p>你可能会觉得，我们不需要在单元测试中引入Spring。对于上面的例子的确可以这么说，它太简单了，AccountServiceImpl 依赖的PersonService完全可以通过setter手工注入。但是实际的开发场景要比这个例子复杂的多，待测试类可能和Spring管理的Beans存在很多关联，它可能依赖于Spring提供的数据源、事务管理器，等等。这些Bean如果都手工管理，将是相当繁琐无味的工作。</p>
<p>使用JUnit 4.x提供的注解<pre class="crayon-plain-tag">@RunWith</pre> ，可以指定单元测试的“运行类”，运行类必须继承自<pre class="crayon-plain-tag">org.junit.runner.Runner</pre> 并实现<pre class="crayon-plain-tag">run</pre> 方法。Spring Test框架提供的运行类是<pre class="crayon-plain-tag">SpringJUnit4ClassRunner</pre> ，使用该类可以轻松的将Spring和JUnit进行集成。该类的用法示例如下：</p>
<pre class="crayon-plain-tag">@RunWith ( SpringJUnit4ClassRunner.class ) //指定单元测试运行类
@ContextConfiguration ( locations = { "applicationContext.xml" } ) //指定Spring配置文件的位置
//很多情况下单元测试离不开事务，下面的注解指明使用的事务管理器
//如果defaultRollback为true，测试运行结束后，默认回滚事务，不影响数据库
@TransactionConfiguration ( transactionManager = "txManager", defaultRollback = true )
@Transactional //指定默认所有测试方法的事务特性
public class AccountServiceTest
{

    @Inject
    private SpringManagedBean bean; //任何Spring管理的Bean都可以注入到单元测试类

    @BeforeClass
    public static void setUpBeforeClass() throws Exception
    {
    }
    @AfterClass
    public static void tearDownAfterClass() throws Exception
    {
    }

    @Before
    public void setUp() throws Exception
    {
    }

    @After
    public void tearDown() throws Exception
    {
    }

    @Repeat ( 10 )//重复测试10次
    //该测试期望抛出IllegalArgumentException，测试超时1秒
    @Test ( expected = IllegalArgumentException.class, timeout = 1000 )
    @Rollback ( true )
    //测试完毕后回滚
    public void test()
    {
    }
}</pre>
<div class="blog_h3"><span class="graybg">AccountService的测试用例</span></div>
<p>依据上一节的知识，我们编写集成Spring Test的测试用例：</p>
<pre class="crayon-plain-tag">import static org.junit.Assert.*;
@RunWith ( SpringJUnit4ClassRunner.class )
@ContextConfiguration ( locations = { "/applicationContext.xml" } )
public class AccountServiceTest
{
    @Inject
    private AccountService accountService;

    @Test
    public void test()
    {
        assertEquals( 68861, accountService.queryBalanceOfDefaultAccount( 100 ) );
    }
}</pre>
<div class="blog_h2"><span class="graybg">基于Mockito进行仿冒</span></div>
<p>引入Spring后，运行单元测试AccountServiceTest，会得到一个NoSuchBeanDefinitionException，这是因为AccountServiceImpl依赖的PersonService没有在Spring中注册。前面我们提到过，PersonService由搭档开发且尚未完成，这个时候要想单独测试AccountServiceImpl，那么就需要开发一个<span style="background-color: #c0c0c0;">模拟</span>的PersonService。最直接的模拟就是实现PersonService接口，但是不方便、工作量大，因此我们引入<span style="background-color: #c0c0c0;">Mock框架：Mockito</span>。</p>
<p>本文不去讨论Mockito的API细节，有兴趣的同学可以参考：<a href="/ut-with-mockito">使用Mockito进行单元测试</a></p>
<p>可以参考如下方式，单独将Mocketo和JUnit集成：</p>
<pre class="crayon-plain-tag">@RunWith ( MockitoJUnitRunner.class ) //运行类
public class AccountServiceTest
{
    //AccountService所依赖的其它对象，会使用Mock注入，因此它引用的PersonService将是一个Mock
    @InjectMocks
    private AccountService accountService = new AccountServiceImpl();
    //自动生成一个PersonService的Mock实现
    @Mock
    private PersonService  personService;
}</pre>
<p>下面的代码则示例了如何把Spring也集成进来：</p>
<pre class="crayon-plain-tag">@RunWith ( SpringJUnit4ClassRunner.class )
//使用Spring提供的运行类
@ContextConfiguration ( locations = { "/applicationContext.xml" } )
public class AccountServiceTest
{
    @InjectMocks //该字段依赖的其它对象（PersonService），将使用仿冒注入
    @Inject //提示该字段本身由Spring自动注入
    private AccountService accountService;
    @Mock //由Mockito仿冒
    private PersonService  personService;
    @Before
    public void setUp()
    {
        //使得Mockito的注解生效
        MockitoAnnotations.initMocks( this );
    }
    @Test
    public void test()
    {
        //这里断点可以看到accountService.personService的类型是：
        //PersonService$$EnhancerByMockitoWithCGLIB$$61056d67
        //这是Mockito生成的仿冒类
        assertEquals( 68861, accountService.queryBalanceOfDefaultAccount( 100 ) );
    }
}</pre>
<p>注意：上面的集成<span style="background-color: #c0c0c0;">并没有解决</span>AccountServiceImpl对PersonService的依赖性，NoSuchBeanDefinitionException还会出现，除非使用Spring提供的“可选”依赖注入：</p>
<pre class="crayon-plain-tag">@Autowired ( required = false )
private PersonService          personService;</pre>
<p>但这种变通方式改变了应用语义，不应该使用。因此，到目前为止我们只能做到：在单元测试中用仿冒代替一个<span style="background-color: #c0c0c0;">既有</span>的Bean。</p>
<div class="blog_h2"><span class="graybg">粘合剂：Springockito</span></div>
<p>Springockito是针对Spring的一个小扩展，它可以简化Mockito仿冒的创建和管理，让Spring与之更无缝的集成：</p>
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;beans  xmlns="http://www.springframework.org/schema/beans" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:context="http://www.springframework.org/schema/context" 
        xmlns:mockito="http://www.mockito.org/spring/mockito"
        
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
        http://www.mockito.org/spring/mockito https://cdn.gmem.cc/schema/spring-mockito.xsd
    "&gt;
    &lt;!-- 创建一个受Spring管理的PersonService仿冒，其它Bean很自然的可以获得注入 --&gt;
    &lt;mockito:mock id="personService" class="cc.gmem.study.sam.service.PersonService" /&gt;
    &lt;!-- 可以监控（Spying）一个Bean，但不影响它的任何行为，注意beanName必须与@Service指定的名称一致 --&gt;
    &lt;mockito:spy beanName="accountService" /&gt;
&lt;/beans&gt;</pre>
<p>现在可以把PersonService作为一个普通的Spring管理的Bean来看待，下面是最终的测试用例：</p>
<pre class="crayon-plain-tag">@Inject
private AccountService accountService;

@Inject
private PersonService  personService;  //注入mock，就像注入普通Bean一样

@Test
public void test()
{
    int id = 0;
    Person alex = new Person( id, "Alex", new Account( 100 ) );
    //对作为Bean的Mock进行打桩，设定后续方法调用的行为，就像为普通Mock打桩一样
    when( personService.getPerson( 0 ) ).thenReturn( alex );
    //验证结果
    assertEquals( 68861, accountService.queryBalanceOfDefaultAccount( id ) );

    //验证queryBalanceOfDefaultAccount方法被调用了一次
    verify( accountService ).queryBalanceOfDefaultAccount( id ); 
    //Mockito要求verify的入参必须是Mock，Springockito解除了这一限制
}</pre>
<p>我们使用了PersonService的Mock，而不要求它已经被实现、注册到Spring； 同时，我们可以对既有的Bean进行监控，而不要求它是一个Mock。</p>
<p> Springockito也提供了与XML配置等价的注解方式：</p>
<pre class="crayon-plain-tag">@RunWith ( SpringJUnit4ClassRunner.class )
//注意：必须修改loader为SpringockitoContextLoader.class
@ContextConfiguration ( loader = SpringockitoContextLoader.class, locations = { "/applicationContext.xml" } )
public class AccountServiceTest
{
    @WrapWithSpy  //mockito:spy
    @Inject
    private AccountService accountService;
    
    @ReplaceWithMock ( beanName = "personService" ) //mockito:mock
    @Inject
    private PersonService  personService;
}</pre>
<div class="blog_h2"><span class="graybg">Spring MVC的测试</span></div>
<p>Spring 3.2的Test子项目提供了类MockMvc，调用其<pre class="crayon-plain-tag">perform()</pre> 方法，可以触发一次“请求”，该调用会返回一个<pre class="crayon-plain-tag">ResultActions</pre> 接口。可以针对ResultActions执行一系列的<span style="background-color: #c0c0c0;">动作</span>和<span style="background-color: #c0c0c0;">断言</span>，或者返回<span style="background-color: #c0c0c0;">处理结果</span><pre class="crayon-plain-tag">MvcResult</pre> ，该接口提供以下方法：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 190px; text-align: center;"> 方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>andExpect(ResultMatcher)</td>
<td>执行期望（断言），一般会配合静态导入：MockMvcRequestBuilders.*、MockMvcResultMatchers.*使用。举例：<br />
<pre class="crayon-plain-tag">mockMvc.perform( get( "/person/1" ) )
//期望HTTP状态码为200
.andExpect( status.isOk() )
//期望响应的MIME类型为JSON
.andExpect( content().mimeType( MediaType.APPLICATION_JSON ) )
//期望响应中的JsonPath对应的值
//JsonPath类似于XPath，可参：http://goessner.net/articles/JsonPath/
.andExpect( jsonPath( "$.person.name" ).equalTo( "Alex" ) );

mockMvc.perform( post( "/form" ) )
//期望请求被重定向到URL
.andExpect( redirectedUrl( "/person/1" ) )
//期望模型属性的数量
.andExpect( model().size( 1 ) )
//期望模型属性的存在性
.andExpect( model().attributeExists( "person" ) );

//模拟请求参数、请求体
mockMvc.perform(
    post( "/form" )
        .param( "name", "Alex" )
        .param( "id", "0" )
        .param( "defacc", "100" )
        .content( "{}" ) //设置请求体，UTF-8字符串
);</pre>
</td>
</tr>
<tr>
<td>andDo(ResultHandler) </td>
<td>执行一个动作，一般会配合静态导入：MockMvcResultHandlers.*。举例：<br />
<pre class="crayon-plain-tag">mockMvc.perform( get( "/form" ) ).andDo( print() );</pre>
</td>
</tr>
<tr>
<td>MvcResult andReturn()</td>
<td>得到Mvc处理结果，从中可以HttpServletRequest、HttpServletResponse、MVC处理器、处理器抛出的异常、HandlerInterceptor、ModelAndView等内容</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">AccountController及其测试用例</span></div>
<p>我们开发一个简单的Controller类，它能够接收查询默认账户余额的请求，并返回一个包含人员、余额信息的映射：</p>
<pre class="crayon-plain-tag">@Controller ( "accountController" )
@RequestMapping ( "/account" )
public class AccountController
{
    @Inject
    private AccountService accountService;

    @RequestMapping ( "/{personId}/defacct/balance" )
    @ResponseBody
    public Map&lt;String, Integer&gt; queryBalanceOfDefaultAccount( @PathVariable int personId, @RequestParam long timestamp )
    {
        int balance = accountService.queryBalanceOfDefaultAccount( personId );
        Map&lt;String, Integer&gt; ret = new LinkedHashMap&lt;String, Integer&gt;();
        ret.put( "personId", personId );
        ret.put( "balance", balance );
        return ret;
    }
}</pre>
<p>假设我们的客户端需要JSON格式的数据，我们可以利用MockMvc来模拟客户端并验证。下面是一个简单的示例（包含JUnit测试类和Spring配置文件）：</p>
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;beans  xmlns="http://www.springframework.org/schema/beans" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:context="http://www.springframework.org/schema/context" 
        xmlns:mockito="http://www.mockito.org/spring/mockito"
        
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
        http://www.mockito.org/spring/mockito https://cdn.gmem.cc/schema/spring-mockito.xsd
    "&gt;
    &lt;context:annotation-config /&gt;
    
    &lt;!-- 该接口尚未实现，必须仿冒 --&gt;
    &lt;mockito:mock id="personService" class="cc.gmem.study.sam.service.PersonService" /&gt;
    
    &lt;!-- 该接口虽已实现，但是为了隔离依赖单独测试MVC部分，我们这里使用仿冒 --&gt;
    &lt;mockito:mock id="accountService" class="cc.gmem.study.sam.service.AccountService" /&gt;
&lt;/beans&gt;</pre><br />
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mockito="http://www.mockito.org/spring/mockito"
       
       xsi:schemaLocation="
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
        http://www.mockito.org/spring/mockito https://cdn.gmem.cc/schema/spring-mockito.xsd
        
    "&gt;
    &lt;!-- 我们需要监控此Bean被调用的情况 --&gt;
    &lt;mockito:spy beanName="accountController" /&gt;
    &lt;context:component-scan base-package="cc.gmem.study.sam.ctrl" /&gt;

    &lt;mvc:annotation-driven /&gt;
    &lt;bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"&gt;
        &lt;property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /&gt;
        &lt;property name="prefix" value="/WEB-INF/jsp/" /&gt;
        &lt;property name="suffix" value=".jsp" /&gt;
        &lt;property name="order" value="0" /&gt;
    &lt;/bean&gt;
&lt;/beans&gt;</pre><br />
<pre class="crayon-plain-tag">import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.springframework.http.MediaType.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith ( SpringJUnit4ClassRunner.class )
//提示该测试使用WebApplicationContext，需要3.2版本
//该注解的value用来说明不带Spring前缀的Resource（例如classpath:）的寻找路径
@WebAppConfiguration ( "src/main/webapp" )
//定义WebApplicationContext的层次，需要3.2.2版本
@ContextHierarchy ( {
        //这里目前不能使用load：SpringockitoContextLoader，可能Springockito注解尚不兼容3.2.2
        //因此我们使用XML方式配置Springockito
        //父上下文
        @ContextConfiguration ( locations = "classpath:applicationContext.xml" ),
        //子上下文
        @ContextConfiguration ( locations = "classpath:applicationContext-mvc.xml" ) 
} )
public class AccountControllerTest
{
    private MockMvc               mockMvc; //需要3.2版本
    
    @Inject
    private WebApplicationContext wac; //最低层次的上下文被注入
    
    @Inject
    private AccountController accountController; //被测试类
    
    @Inject
    private AccountService accountService;
    
    @Before
    public void setUp() throws Exception
    {
        //初始化MockMvc
        mockMvc = MockMvcBuilders.webAppContextSetup( wac ).build();
    }

    @Test
    public void test() throws Exception
    {
        int personId = 0;
        int balance = 1000;
        long timestamp = System.currentTimeMillis();
        
        /*仿冒打桩*/
        when(accountService.queryBalanceOfDefaultAccount( personId )).thenReturn( balance);
        
        /*HTTP请求模拟以及结果验证*/
        mockMvc.perform( 
                get( "/account/{personId}/defacct/balance", personId )
                .accept( APPLICATION_JSON ) //请求返回JSON格式的响应
                //设置请求头
                .header( "JSESSIONID", new Object[]{"aue60a2p2m8fe5s0t2m1am78t4"} )
                //设置请求参数
                .param( "timestamp", String.valueOf( timestamp ) )
        )
        .andExpect( status().isOk() )
        .andExpect( content().contentTypeCompatibleWith( APPLICATION_JSON ) ) //期望返回JSON格式的响应
        .andExpect( jsonPath( "$.balance" ).value( balance ) ) //JSONPath验证
        .andDo( print() ); //打印请求、响应和处理过程的详细信息，以便核查
        
        /*Mockito验证*/
        //验证accountController恰好被调用了一次
        verify( accountController ).queryBalanceOfDefaultAccount( personId, timestamp );
        //验证AccountService至少被调用了一次
        verify( accountService,atLeast( 1 ) ).queryBalanceOfDefaultAccount( personId );
        
        /*Junit验证*/
        assertTrue( true );
    }
}</pre>
<p>可以看到，我们在测试用例中基于MockMvc提供的丰富API，来构建仿冒的请求，并验证Spring MVC的响应。同时，我们使用Mockito来隔离AccountService服务，简化了依赖管理。 </p>
<div class="blog_h2"><span class="graybg">附录</span></div>
<p>本文使用的Maven POM依赖配置如下：</p>
<pre class="crayon-plain-tag">&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;javax.annotation&lt;/groupId&gt;
        &lt;artifactId&gt;jsr250-api&lt;/artifactId&gt;
        &lt;version&gt;1.0&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;javax.inject&lt;/groupId&gt;
        &lt;artifactId&gt;javax.inject&lt;/artifactId&gt;
        &lt;version&gt;1&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;javax.servlet&lt;/groupId&gt;
        &lt;artifactId&gt;servlet-api&lt;/artifactId&gt;
        &lt;version&gt;2.4&lt;/version&gt;
        &lt;scope&gt;provided&lt;/scope&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;javax.servlet.jsp&lt;/groupId&gt;
        &lt;artifactId&gt;jsp-api&lt;/artifactId&gt;
        &lt;version&gt;2.0&lt;/version&gt;
        &lt;scope&gt;provided&lt;/scope&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;javax.servlet&lt;/groupId&gt;
        &lt;artifactId&gt;jstl&lt;/artifactId&gt;
        &lt;version&gt;1.2&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;taglibs&lt;/groupId&gt;
        &lt;artifactId&gt;standard&lt;/artifactId&gt;
        &lt;version&gt;1.1.2&lt;/version&gt;
        &lt;scope&gt;provided&lt;/scope&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;javax.el&lt;/groupId&gt;
        &lt;artifactId&gt;el-api&lt;/artifactId&gt;
        &lt;version&gt;2.2&lt;/version&gt;
        &lt;scope&gt;provided&lt;/scope&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework&lt;/groupId&gt;
        &lt;artifactId&gt;spring-core&lt;/artifactId&gt;
        &lt;version&gt;3.2.3.RELEASE&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework&lt;/groupId&gt;
        &lt;artifactId&gt;spring-jdbc&lt;/artifactId&gt;
        &lt;version&gt;3.2.3.RELEASE&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework&lt;/groupId&gt;
        &lt;artifactId&gt;spring-beans&lt;/artifactId&gt;
        &lt;version&gt;3.2.3.RELEASE&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework&lt;/groupId&gt;
        &lt;artifactId&gt;spring-orm&lt;/artifactId&gt;
        &lt;version&gt;3.2.3.RELEASE&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework&lt;/groupId&gt;
        &lt;artifactId&gt;spring-webmvc&lt;/artifactId&gt;
        &lt;version&gt;3.2.3.RELEASE&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt;
        &lt;artifactId&gt;jackson-core&lt;/artifactId&gt;
        &lt;version&gt;2.0.4&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt;
        &lt;artifactId&gt;jackson-databind&lt;/artifactId&gt;
        &lt;version&gt;2.0.4&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt;
        &lt;artifactId&gt;jackson-annotations&lt;/artifactId&gt;
        &lt;version&gt;2.0.4&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;log4j&lt;/groupId&gt;
        &lt;artifactId&gt;log4j&lt;/artifactId&gt;
        &lt;version&gt;1.2.15&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
        &lt;artifactId&gt;slf4j-log4j12&lt;/artifactId&gt;
        &lt;version&gt;1.6.1&lt;/version&gt;
    &lt;/dependency&gt;


    &lt;dependency&gt;
        &lt;groupId&gt;junit&lt;/groupId&gt;
        &lt;artifactId&gt;junit&lt;/artifactId&gt;
        &lt;version&gt;4.11&lt;/version&gt;
        &lt;scope&gt;test&lt;/scope&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework&lt;/groupId&gt;
        &lt;artifactId&gt;spring-test&lt;/artifactId&gt;
        &lt;version&gt;3.2.3.RELEASE&lt;/version&gt;
        &lt;scope&gt;test&lt;/scope&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.mockito&lt;/groupId&gt;
        &lt;artifactId&gt;mockito-all&lt;/artifactId&gt;
        &lt;version&gt;1.9.0&lt;/version&gt;
        &lt;scope&gt;test&lt;/scope&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.kubek2k&lt;/groupId&gt;
        &lt;artifactId&gt;springockito&lt;/artifactId&gt;
        &lt;version&gt;1.0.9&lt;/version&gt;
        &lt;scope&gt;test&lt;/scope&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.kubek2k&lt;/groupId&gt;
        &lt;artifactId&gt;springockito-annotations&lt;/artifactId&gt;
        &lt;version&gt;1.0.9&lt;/version&gt;
        &lt;scope&gt;test&lt;/scope&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;com.jayway.jsonpath&lt;/groupId&gt;
        &lt;artifactId&gt;json-path-assert&lt;/artifactId&gt;
        &lt;version&gt;0.9.1&lt;/version&gt;
        &lt;scope&gt;test&lt;/scope&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/ut-with-spring-and-mockito">基于Spring Test和Mockito进行单元测试</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/ut-with-spring-and-mockito/feed</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>使用Mockito进行单元测试</title>
		<link>https://blog.gmem.cc/ut-with-mockito</link>
		<comments>https://blog.gmem.cc/ut-with-mockito#comments</comments>
		<pubDate>Sat, 11 Feb 2012 08:23:00 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Test]]></category>
		<category><![CDATA[Mock]]></category>
		<category><![CDATA[单元测试]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=8196</guid>
		<description><![CDATA[<p>Mockito简介 Mockito是一个Java仿冒框架，所谓仿冒，就是创建一个“虚假”的类，来模拟一个类的行为。之所以需要“仿冒”，可能出于下列考虑： 某个类已经进入单元测试，而协作类尚未开发完成。此时需要快速模拟出协作类，避免耽误测试进度 协作类的实现可能存在缺陷，这是可以使用仿冒隔离其影响，仿冒严格的按照接口规约返回处理结果 模拟难以在单元测试阶段获得实例的对象，这些对象往往和环境、容器相关，例如HttpServletRequest Mockito关注三件事件：Mock的创建、验证（verification）和打桩（stubbing）。所谓验证，就是断言预期的行为（方法调用）发生；所谓打桩，就是设置Mock在特定条件（入参）下的行为（返回值、异常）。 功能说明 简单的例子 下面的小例子说明何为验证、打桩： [crayon-69d314daf26c2516771654/] 可以看到，都Mock的每一种调用，我们都可以监控到；相应的Mock可以迅速、准确的模拟出真实对象的行为， 而忽略任何行为的实现细节。 未打桩前，Mock这样处理返回值： 返回Object的方法，Mock默认返回null 返回集合的方法，Mock默认返回空集合 返回数字的方法，Mock默认返回0 返回布尔的方法，Mock默认返回false 对Mock的打桩可以进行多次，后面的会覆盖前面的打桩。 参数匹配 对Mock进行打桩或者验证时时，可以有多种匹配入参的方式： [crayon-69d314daf26c5707670211/] 参数捕获 <a class="read-more" href="https://blog.gmem.cc/ut-with-mockito">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/ut-with-mockito">使用Mockito进行单元测试</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">Mockito简介</span></div>
<p>Mockito是一个Java仿冒框架，所谓仿冒，就是创建一个“虚假”的类，来模拟一个类的行为。之所以需要“仿冒”，可能出于下列考虑：</p>
<ol>
<li>某个类已经进入单元测试，而协作类<span style="background-color: #c0c0c0;">尚未开发完成</span>。此时需要<span style="background-color: #c0c0c0;">快速模拟</span>出协作类，避免耽误测试进度</li>
<li>协作类的实现可能存在缺陷，这是可以使用仿冒<span style="background-color: #c0c0c0;">隔离其影响</span>，仿冒严格的按照接口规约返回处理结果</li>
<li>模拟<span style="background-color: #c0c0c0;">难以在单元测试阶段获得</span>实例的对象，这些对象往往和环境、容器相关，例如HttpServletRequest</li>
</ol>
<p>Mockito关注三件事件：Mock的创建、验证（verification）和打桩（stubbing）。所谓验证，就是断言预期的行为（方法调用）发生；所谓打桩，就是设置Mock在特定条件（入参）下的行为（返回值、异常）。</p>
<div class="blog_h2"><span class="graybg">功能说明</span></div>
<div class="blog_h3"><span class="graybg">简单的例子</span></div>
<p>下面的小例子说明何为验证、打桩：</p>
<pre class="crayon-plain-tag">import static org.mockito.Mockito.*;
import static org.junit.Assert.*;

//制作一个仿冒
Map&lt;String, Object&gt; map = mock( Map.class );

//对仿冒进行调用
map.put( "Key", 1024 );
map.clear();

//验证：仿冒的某个方法以指定的参数被调用一次
verify( map ).put( "Key", new Integer( 1024 ) ); //只进行equals匹配

//验证：仿冒的某个方法至少被调用一次
verify( map, atLeast( 1 ) ).clear();

//打桩：尚未打桩的入参调用，返回null
assertEquals( null, map.get( "Any" ) );
//打桩：当以Hello为键获取值时，返回World
when( map.get( "Hello" ) ).thenReturn( "World" );
assertEquals( "World", map.get( "Hello" ) );</pre>
<p>可以看到，都Mock的每一种调用，我们都可以监控到；相应的Mock可以迅速、准确的模拟出真实对象的行为， 而忽略任何行为的实现细节。</p>
<p>未打桩前，Mock这样处理返回值：</p>
<ol>
<li>返回Object的方法，Mock默认返回null</li>
<li>返回集合的方法，Mock默认返回空集合</li>
<li>返回数字的方法，Mock默认返回0</li>
<li>返回布尔的方法，Mock默认返回false</li>
</ol>
<p>对Mock的打桩可以进行多次，后面的会覆盖前面的打桩。</p>
<div class="blog_h3"><span class="graybg">参数匹配</span></div>
<p>对Mock进行打桩或者验证时时，可以有多种匹配入参的方式：</p>
<pre class="crayon-plain-tag">List list = mock( List.class );
when( list.get( anyInt() ) ).thenReturn( null );//任何整数参数
when( list.addAll( anyInt(), anyCollection() ) ).thenReturn( null );//两个任意参数
//实现参数匹配器接口，进行任意复杂度的匹配
when( list.get( argThat( new ArgumentMatcher&lt;Integer&gt;() {
    @Override
    public boolean matches( Object item )
    {
        Integer index = (Integer) item;
        return index &lt; 100;
    }
} ) ) ).thenReturn( null );</pre>
<div class="blog_h3"><span class="graybg">参数捕获</span></div>
<pre class="crayon-plain-tag">List list = mock( List.class );
ArgumentCaptor&lt;Integer&gt; argument = ArgumentCaptor.forClass( Integer.class );
list.add( 1 );//这个参数会被捕获
verify( list ).get( argument.capture() ); //执行捕获
//后续对参数进行进一步验证
assertEquals( new Integer( 1 ), argument.getValue() );</pre>
<div class="blog_h3"><span class="graybg">返回值设定</span></div>
<pre class="crayon-plain-tag">List list = mock( List.class );
//返回固定值
when( list.get( 0 ) ).thenReturn( 10 );
//设置每次调用返回不同值
when( list.get( 0 ) ).thenReturn( 1, 2, 3 );
//抛出异常，不返回
when( list.get( 0 ) ).thenThrow( new RuntimeException() );
//依据入参决定返回值
when( list.get( anyInt() ) ).then( new Answer&lt;Object&gt;() {

    public Object answer( InvocationOnMock invocation ) throws Throwable
    {
        return "El-" + invocation.getArguments()[0];
    }
} );
//在部分仿冒时，调用真实方法
when( list.get( 0 ) ).thenCallRealMethod();

//连续仿冒
when( list.get( 0 ) )
        .thenReturn( 1 ) //第一次调用的返回值
        .thenReturn( 2 )//第二次调用的返回值
        .thenThrow( new RuntimeException() ); //以后调用的返回值

//设置未打桩方法的默认返回值
mock( List.class, new Answer&lt;Object&gt;() {

    @Override
    public Object answer( InvocationOnMock invocation ) throws Throwable
    {
        return null;
    }
} );</pre>
<div class="blog_h3"><span class="graybg">void方法打桩</span></div>
<pre class="crayon-plain-tag">//什么都不作
doNothing().when( list ).clear();
//抛出异常
doThrow( new RuntimeException() ).when( list ).clear();</pre>
<div class="blog_h3"><span class="graybg">调用次数验证</span></div>
<pre class="crayon-plain-tag">List list = mock( List.class );
//验证基于某个精确实参的调用发生的次数
verify( list, times( 1 ) ).add( 1 ); //一次
verify( list, times( 2 ) ).add( 2 ); //二次
verify( list, atLeastOnce() ).add( 1 ); //至少一次
verify( list, atLeast( 3 ) ).add( 3 ); //至少三次
verify( list, atMost( 3 ) ).add( 3 ); //至多三次
//参数匹配也适用于验证的时候：
verify( list, atMost( 3 ) ).add( argThat( new ArgumentMatcher&lt;Object&gt;() {
    public boolean matches( Object argument )
    {
        return false;
    }
} ) );

//断言Mock上没有发生任何未验证的调用
verifyNoMoreInteractions( list );</pre>
<div class="blog_h3"><span class="graybg">监控真实对象</span></div>
<pre class="crayon-plain-tag">List&lt;Integer&gt; list = new ArrayList&lt;Integer&gt;();
List&lt;Integer&gt; spy = spy( list );

//对真实对象进行打桩
when( spy.size() ).thenReturn( 100 );

//另外一种打桩方式，防止异常
doReturn( 1 ).when( spy ).get( 0 );
//下面这种方式是不行的，因为未打桩前会调用真实方法，导致IndexOutOfBoundsException 
when( spy.get( 0 ) ).thenReturn( 1 );


spy.add( 1 );//调用真实的方法
spy.size();//调用仿冒的方法
verify( spy ).add( 1 ); //验证调用了一次</pre>
<div class="blog_h3"><span class="graybg">部分仿冒</span></div>
<p>有两种方式进行部分仿冒：Spy一个对象，或者Mock一个类（非接口）：</p>
<pre class="crayon-plain-tag">List list = spy(new LinkedList());
List list = mock(LinkedList.class);
//调用真实对象的方法
when(list.clear()).thenCallRealMethod();</pre>
<div class="blog_h3"><span class="graybg">注解的支持</span></div>
<pre class="crayon-plain-tag">//如果要启用注解支持，可以使用该运行类
//或者手工调用MockitoAnnotations.initMocks(this)
@RunWith ( MockitoJUnitRunner.class )
public class MockitoTest
{
    //自动创建Mock
    @Mock
    private List&lt;Object&gt; list;

}</pre>
<div class="blog_h3"><span class="graybg">其它</span></div>
<pre class="crayon-plain-tag">List mock = mock(List.class);
when(mock.size()).thenReturn(10);

reset(mock);  //重置仿冒

//单行调用即完成的Mock
Car boringStubbedCar = when(mock(Car.class).shiftGear()).thenThrow(EngineNotStarted.class).getMock();</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/ut-with-mockito">使用Mockito进行单元测试</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/ut-with-mockito/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
