Springboot 單元測試詳解

1.Springboot的測試類庫

SpringBoot 提供了許多實用工具和注解來幫助測試應(yīng)用程序蛙粘,主要包括以下兩個模塊思劳。

  • spring-boot-test: 支持測試的核心內(nèi)容。
  • spring-boot-test-autoconfigure:支持測試的自動化配置逗宁。

開發(fā)進(jìn)行只要引入spring-boot-starter-test的依賴 就能引入這些SpringBoot測試模塊盯滚,還能引入一些像Junit,AssertJ奸晴,Hamcrest及其他一些有用的類庫,具體如下所示日麸。

  • Junit: Java應(yīng)用程序單元測試標(biāo)準(zhǔn)類庫寄啼。
  • Spring Test & Spring Boot Test: Spring Boot 應(yīng)用程序功能集成化測試支持。
  • AssertJ: 一個輕量級斷言類庫代箭。
  • Hamcrest: 一個對象匹配器類庫墩划。
  • Mockito: 一個java Mock測試框架。
  • JSONassert: 一個用于JSON的斷言庫嗡综。
  • JsonPath: 一個Json操作類庫乙帮。

Maven 依賴

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

2.創(chuàng)建測試類和測試方法

要讓一個普通類變成一個單元測試類:

  • 1.在雷鳴上加入@SpringBootTest 和@RunWith(SpringRunner.class)兩個注解即可。
  • 2.在測試方法上加上@Test注解极景。

使用IDEA可以使用快捷鍵ctrl + shift + t 或者選中當(dāng)前類名 使用快捷鍵alt + enter ,向下選擇Create Test 即可進(jìn)入測試類的選項中察净,再次回車,就快速的生成測試類戴陡。

快速生成測試類界面

生成的測試類在src/test目錄下塞绿,測試類和源代碼寶明是一致的沟涨。

生成后的測試類

3.JUnit4

JUnit4中的注解

  • @BeforeClass:針對所有測試恤批,只執(zhí)行一次,且必須為static void
  • @Before:初始化方法裹赴,執(zhí)行當(dāng)前測試類的每個測試方法后執(zhí)行
  • @Test:測試方法喜庞,在這里可以測試期望異常和超時時間
  • @After:釋放資源,執(zhí)行當(dāng)前測試類的每個測試方法后執(zhí)行
  • @AfterClass:針對所有測試棋返,只執(zhí)行一次延都,且必須為static void
  • Ignore :忽略的測試方法
  • @Runwith:可以更改測試運行器,缺省值org.junit.runner.Runner

一個單元測試類執(zhí)行順序為:
@BeforeClass -> @Before -> @Test -> @After -> @AfterClass

每一個測試方法的調(diào)用順序為:
@Before -> @Test -> @After

3.1超時測試

如果一個測試用例比起指定的毫秒數(shù)要花費更多時間睛竣,那么JUnit將自動將他標(biāo)記為失敗晰房,timeout參數(shù)和@test注解一起使用。現(xiàn)在讓我們看看活動中的@Test(timeout)

@Test(timeout = 1000)
public void testTimeout() throws InterruptedException {
    TimeUnit.SECONDS.sleep(2);
    System.out.println("Complete");
}

上面測試會失敗,在一秒后會拋出異常org.junit.runners.model.TestTimeOutException:test timedout after 1000 millseconds

3.2異常測試

你可以測試代碼是否它拋出想要得到的異常殊者。expected參數(shù)和@Test 注釋一起使用∮刖常現(xiàn)在讓我們看看活動中的@Test(expected)

    @Test(expected = NullPointerException.class)
    public void testNullException() {
        throw new NullPointerException();
    }

3.3套件測試

public class TaskOneTest {
    @Test
    public void test(){
        System.out.println("task one do");
    }
}

public class TaskTwoTest {
    @Test
    public void test(){
        System.out.println("task two do");
    }
}

public class TaskThreeTest {
    @Test
    public void test(){
        System.out.println("task three do");
    }
}

/*1. 更改測試運行方式為 Suite*/
@RunWith(Suite.class)
/*2. 將測試類傳入進(jìn)來*/
@Suite.SuiteClasses({TaskOneTest.class,TaskTwoTest.class,TaskThreeTest.class})
public class SuitTest {
    /*測試套件的入口類知識組織測試類一起進(jìn)行測試,無任何測試方法*/
}

3.4參數(shù)化測試

JUnit4 引入了一個新的功能參數(shù)化測試猖吴。參數(shù)化測試允許開發(fā)人員使用不同的值反復(fù)運行同一個測試摔刁。
創(chuàng)建參數(shù)化測試,遵循以下5個步驟海蔽。

  • RunWith(Parameterized.class)來注釋test類共屈。
  • 創(chuàng)建一個由@Parameters注釋的公共的靜態(tài)方法,它返回一個對象的集合(數(shù)組)來作為數(shù)據(jù)集合党窜。
  • 創(chuàng)建一個公共的構(gòu)造函數(shù)拗引,它接受數(shù)據(jù)集合相同的參數(shù)。
  • 為每一列測試數(shù)據(jù)創(chuàng)建一個實例變量幌衣。
  • 用實例變量作為測試數(shù)據(jù)的來源來創(chuàng)建你的測試用例寺擂。
/*更改默認(rèn)的測試運行器為RunWith(Parameterized.class)*/
@RunWith(Parameterized.class)
public class ParameterTest {

    /*聲明變量存放預(yù)期值和測試數(shù)據(jù)*/
    private String firstName;
    private String lastName;

    /*聲明一個返回值 為Collection的公共靜態(tài)方法,并使用@Parameters進(jìn)行修飾*/
    @Parameterized.Parameters
    public static List<Object[]> param() {
        /*這里給出兩個測試用例*/
        return Arrays.asList(new Object[][]{{"Mike","Black"},{"Circle","Smith"}});
    }

    public ParameterTest (String firstName,String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    /*進(jìn)行測試泼掠,發(fā)現(xiàn)它會將所有的測試用例測試一遍*/
    @Test
    public void test(){
        String name = firstName + " " + lastName;
        System.out.println(name);
    }


4.Assert

assert常用方法

  • assertEquals("message",A,B):判斷對象A和B是否相等僵控,這個判斷比較時調(diào)用了equals()方法。
  • assertSame("message",A,B):判斷對象A和B是否相同浅碾,使用的是==操作符影暴。
  • assertTure("message",A):判斷A條件是否為真。
  • assertFalse("message",A):判斷A條件是否不為真腻豌。
  • assertNotNull("message",A):判斷A對象是否不為null
  • assertArrayEquals("message",A,B): 判斷A數(shù)組與B數(shù)組是否相等家坎。

5.Mockito

什么是mock
在面向?qū)ο蟮某绦蛟O(shè)計中,模擬對象(mock object)是以可控的方式模擬真實對象行為的假對象吝梅。在編程過程中虱疏,通常通過模擬一些輸入數(shù)據(jù),來驗證程序是否達(dá)到預(yù)期結(jié)果苏携。

為什么使用Mock對象
使用模擬對象做瞪,可以模擬復(fù)雜的、真實的對象行為右冻。如果在單元測試中無法使用真實對象装蓬,可采用模擬對象進(jìn)行替代。

在以下情況可以采用模擬對象來替代真實對象:

  • 真實對象的行為是不確定的(例如纱扭,當(dāng)前的時間或溫度)牍帚。
  • 真實對象很難搭建起來。
  • 真實對象的行為很難觸發(fā)(例如乳蛾,網(wǎng)絡(luò)錯誤)暗赶。
  • 真實對象速度很難鄙币。
  • 真實對象是用戶界面,或包括用戶界面在內(nèi)蹂随。
  • 真實的對象使用了回調(diào)機制爱榔。
  • 真實對象可能還不存在。
  • 真實對象可能包含不能用作測試的信息和方法糙及。

使用Mockito一般分為三個步驟:

  • 1.模擬測試類所需的外部依賴
  • 2.執(zhí)行測試代碼
  • 3.判斷執(zhí)行結(jié)果是否達(dá)到預(yù)期

Mockito
JUnit和SpringTest基本上可以滿足絕大多數(shù)單元測試详幽,但是由于現(xiàn)在系統(tǒng)越來越復(fù)雜,相互之間依賴越來越多浸锨。特別是微服務(wù)化以后的系統(tǒng)唇聘,往往一個模塊的代碼需要依賴幾個其他模塊的東西。因此柱搜,在做單元測試的時候迟郎,往往很難構(gòu)造出需要的依賴。一個單元測試聪蘸,我們只關(guān)心一個小的功能宪肖,但是為了這個小的功能能跑起來,可能需要依賴一堆其他的東西健爬,這就導(dǎo)致了單元測試無法進(jìn)行控乾。所以我們就需要在測試過程中引入mock測試。

所謂的Mock測試就是在測試過程中娜遵,對于一些不容易構(gòu)造的蜕衡、或者和這次單元測試無關(guān)但是上下文又有依賴的對象,用一個虛擬的對象(Mock對象)來模擬设拟,以便單元測試能夠進(jìn)行慨仿。
比如有一段代碼的依賴為:

代碼依賴

當(dāng)我們要進(jìn)行單元測試的時候,就需要給A注入BC但是C又依賴了D纳胧,D又依賴了E镰吆。這就導(dǎo)致了A的單元測試很難進(jìn)行。

但是當(dāng)我們使用Mock來進(jìn)行模擬對象后跑慕,我們就可以把這種依賴解耦万皿,只關(guān)心A本身的測試,它所依賴的B和C相赁,全部使用Mock出來的對象相寇,并且給MockBMockC指定一個明確的行為慰于。就像這樣:

Mock對象示意圖

因此钮科,當(dāng)我們使用Mock后,對于那些難以構(gòu)建的對象婆赠,就變成了個模擬對象绵脯,只需要提前的做Stubbing(樁)即可佳励。所謂的做樁數(shù)據(jù),也就是告訴Mock對象蛆挫,當(dāng)與之交互時執(zhí)行何種行為過程赃承。比如當(dāng)調(diào)用B對象的b()方法時,我們期望返回一個true,這就是一個設(shè)置樁數(shù)據(jù)的預(yù)期悴侵。

mockito 使用詳解

現(xiàn)有如下代碼:
實體類

@Entity
@Data
@NoArgsConstructor
public class User implements Serializable{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true,nullable = false,length = 50)
    private String username;

    private String password;

    @CreationTimestamp
    private Date createDate;

    public User(Long id,String username) {
        this.id = id;
        this.username = username;
    }
}

Repository

public interface IUserRepository extends JpaRepository<User,Long>{
    boolean updateUser(User user);
}

Service

@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class UserServiceImpl implements IUserService {
    private final IUserRepository userRepository;


    @Override
    public User findOne(Long id) {
        return userRepository.getOne(id);
    }

    @Override
    public boolean updateUsername(Long id, String username) {
        User user = findOne(id);
        if(user == null) {
            return false;
        }
        user.setUsername(username);
        return userRepository.updateUser(user);
    }
}

Test

public class IUserServiceTest {
    private IUserService userService;

//    @Mock
    private IUserRepository userRepository;


    @Before
    public void setUp() throws Exception {
        /*對所有注解了@Mock的對象進(jìn)行模擬*/
//        MockitoAnnotations.initMocks(this);
        /*如果不使用注解瞧剖,可以對單個對象進(jìn)行mock*/
        userRepository = Mockito.mock(IUserRepository.class);
        /*構(gòu)造測試對象*/
        userService = new UserServiceImpl(userRepository);
        /*打樁,構(gòu)建當(dāng)userRepository getOne函數(shù)執(zhí)行參數(shù)為1的時候可免,設(shè)置返回的結(jié)果User*/
        Mockito.when(userRepository.getOne(1L)).thenReturn(new User(1L,"jack"));
         /*打樁抓于,構(gòu)建當(dāng)userRepository getOne函數(shù)執(zhí)行參數(shù)為1的時候,設(shè)置返回的結(jié)果null*/
        Mockito.when(userRepository.getOne(2L)).thenReturn(null);
         /*打樁浇借,構(gòu)建當(dāng)userRepository getOne函數(shù)執(zhí)行參數(shù)為1的時候捉撮,設(shè)置拋出異常*/
        Mockito.when(userRepository.getOne(3L)).thenThrow(new IllegalArgumentException("the id is not support"));
         /*打樁,構(gòu)建當(dāng)userRepository updateUser執(zhí)行任意User類型的參數(shù)妇垢,返回的結(jié)果都是true*/
        Mockito.when(userRepository.updateUser(Mockito.any(User.class))).thenReturn(true);
        /*打樁巾遭,給void方法 */
        Mockito.doAnswer(invocation -> {
            System.out.println("進(jìn)入Mock");
        return null;
        }).when(userRepository).addUser(Mockito.any());

        /*模擬方法設(shè)置返回期望值*/
        List spy = Mockito.spy(new LinkedList<>());
        /*這里會拋出IndexOutOfBoundsException*/
//        Mockito.when(spy.get(0)).thenReturn("foo");
        /*所以要使用下面代碼*/
        Mockito.doReturn("foo").when(spy).get(0);
    }



    @Test
    public void testUpdateUsernameSuccess() throws Exception {
        Long userId = 1L;
        String newUsername = "new Jack";
        /*測試service方法*/
        boolean updated = userService.updateUsername(userId,newUsername);
        /*檢查結(jié)果*/
        Assert.assertThat(updated, Matchers.is(true));

        /*Mock對象一旦創(chuàng)建,就會自動記錄自己的交互行為闯估。通過verify(mock).someMethod()方法灼舍,來驗證方法是否被調(diào)用。*/
        /*驗證調(diào)用上面的service方法后是否 userRepositroy.getOne(1L)調(diào)用過涨薪。*/
        Mockito.verify(userRepository).getOne(userId);

        /*updateUsername 函數(shù)中我們調(diào)用了已經(jīng)打樁了的其他的函數(shù)片仿,現(xiàn)在我們來驗證進(jìn)入其他函數(shù)中的參數(shù)*/
        /*構(gòu)造參數(shù)捕獲器,用于捕獲方法參數(shù)進(jìn)行驗證*/
        ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
        /*驗證updateUser方法是否唄調(diào)用過尤辱,并且捕獲入?yún)?/
        Mockito.verify(userRepository).updateUser(userCaptor.capture());
        /*獲取參數(shù)updateUser*/
        User updateUser = userCaptor.getValue();
        /*驗證入?yún)⑹欠袷穷A(yù)期的*/
        Assert.assertThat(updateUser.getUsername(),Matchers.is(newUsername));
        /*保證這個測試用例中所有被Mock的對象的相關(guān)方法都已經(jīng)被Verify過了*/
        Mockito.verifyNoMoreInteractions(userRepository);
        /*如果有一個交互沒有被verify砂豌,則會報錯
        org.mockito.exceptions.verification.NoInteractionsWanted:
        No interactions wanted here:
        -> at com.wuwii.service.IUserServiceTest.testUpdateUsernameSuccess(IUserServiceTest.java:74)
        But found this interaction on mock 'iUserRepository':
        -> at com.wuwii.service.impl.UserServiceImpl.findOne(UserServiceImpl.java:21)
        ****/

    }

//    @Test
    public void testUpdateUsernameFailed() throws Exception {
        Long userId = 2L;
        String newUsername = "new Jack";
        /*沒有經(jīng)過mock的updateUser方法,它的返回值是false*/
        boolean updated = userService.updateUsername(userId,newUsername);
        Assert.assertThat(updated,Matchers.is(true));
        /*驗證userRepository的getOne(2L)這個方法是否被調(diào)用過(這個是被測試過的光督,此步驟通過)*/
        Mockito.verify(userRepository).getOne(2L);
        /*驗證userRepository的updateUser(null)這個方法是否被調(diào)用過(這個方法是沒有被調(diào)用過的)*/
        Mockito.verify(userRepository).updateUser(null);
        Mockito.verifyNoMoreInteractions(userRepository);

    }

創(chuàng)建Mock對象
我們需要對userService進(jìn)行測試阳距,就需要模擬userRepository對象
我們在setUp()方法中,模擬對象并打樁。

模擬對象有兩種方式:

  • 1.對注解@Mock的對象進(jìn)行模擬MockitoAnnotations.initMocks(this)
  • 2.對單個對象手動Mock: userRepositroy = Mockito.mock(IUserRepositroy.class)

數(shù)據(jù)打樁
數(shù)據(jù)打樁结借,方法非常多筐摘,主要分下面幾種:

  • 1.最基本的用法就是調(diào)用when以及thenReturn方法了。它的作用就是指定當(dāng)我們調(diào)用被代理的對象的某一個方法以及參數(shù)的時候船老,返回什么值咖熟。

  • 2.提供參數(shù)匹配器,靈活匹配參數(shù)柳畔。any()馍管、any(Class<T> type)anyBoolean()薪韩、 anyByte() anyChar()确沸、 anyInt()捌锭、 anyLong()等等,它支持復(fù)雜的過濾罗捎,可以使用正則 Mockito.matches(".*User$")观谦,開頭結(jié)尾驗證 endsWith(String suffix)startsWith(String prefix)桨菜,判空驗證 isNotNull()豁状、 isNull() 。也還可以使用argThat(ArgumentMatchermatcher),如:ArgumentMatcher只有一個方法boolean matches(T argument);傳入入?yún)⒌沟茫祷匾粋€boolean表示是否匹配替蔬。Mockito.argThat(argument -> argument.getUsername.length() > 6)

  • 3.Mockito還提供了了兩個表示行為的方法:thenAnswer(Answer<?> answer);thenCallRealMethod();分別表示自定義處理調(diào)用后的行為屎暇,以及調(diào)用真實的方法承桥。這兩個方法在有些測試用例中還是很有用的。

  • 4.對于同一個方法根悼,Mockito可以是順序與次數(shù)關(guān)連的凶异。也就是說可以實現(xiàn)同一個方法 ,第一次調(diào)用返回一個值挤巡,第二次調(diào)用返回一個值剩彬,甚至第三次調(diào)用拋出異常等等。只需要連續(xù)的調(diào)用thenXXXX即可矿卑。

  • 如果為一個返回Void的方法設(shè)置樁數(shù)據(jù)喉恋。上面的方法都是表示的是有返回值的方法,而由于一個方法沒有返回值母廷,因此我們不能調(diào)用when方法轻黑,比如:doAnswer(Answer answer)doNothing()琴昆、doReturn(Object toBeReturned)氓鄙、doThrow(Class<? extends Throwable> toBeThrown)doCallRealMethod()业舍。它們使用方法其實和上面thenXXXX是一樣的.

        /*打樁抖拦,給void方法 */
        Mockito.doAnswer(invocation -> {
            System.out.println("進(jìn)入Mock");
        return null;
        }).when(userRepository).addUser(Mockito.any());
        /*模擬方法設(shè)置返回期望值*/
        List spy = Mockito.spy(new LinkedList<>());
        /*這里會拋出IndexOutOfBoundsException*/
//      Mockito.when(spy.get(0)).thenReturn("foo");
        /*所以要使用下面代碼*/
        Mockito.doReturn("foo").when(spy).get(0);

驗證測試方法的結(jié)果
使用斷言來檢查結(jié)果。

驗證Mock對象的調(diào)用
其實舷暮,在這里我們?nèi)绻皇球炞C方法結(jié)果的正確的話态罪,就非常簡單,但是在復(fù)雜的方法調(diào)用堆棧中下面,往往可能出現(xiàn)結(jié)果正確复颈,但是過程不正確的情況。比如updateUsername方法返回false有兩種可能诸狭,一直可能是用戶沒有找到券膀,還有一種可能就是userRepository.updateUser(userPO)返回false君纫。因此如果我們只使用Assert.assertFalse(updated);來驗證結(jié)果驯遇,可能就會忽略某些錯誤芹彬。

因此我們在測試中還需要驗證指定的方法userRepository.getOne(userId);是否運行過,而且我們還是用了參數(shù)捕獲器叉庐,抓取中間的方法參數(shù)舒帮。用來驗證。

提供了verify(T mock,VerificationMode mode)方法陡叠。VerificationMode有很多作用玩郊。

/*驗證指定方法 get(3) 沒有被調(diào)用*/
verify(mock,never()).get(3);

verifyZeroInteractionsverifyNoMoreInteractions驗證所有mock的方法是否都調(diào)用過了。


MockMvc

MockMvc是由spring-test包提供枉阵,實現(xiàn)了對Http請求的模擬译红,能夠直接使用網(wǎng)絡(luò)的形勢,轉(zhuǎn)換到Controller的調(diào)用兴溜,是的測試速度快侦厚,不依賴網(wǎng)絡(luò)環(huán)境。同時提供了一套驗證的工具拙徽,結(jié)果的驗證十分方便刨沦。
接口MockMvcBuilder,一共一個唯一的build方法膘怕,用來構(gòu)造MockMvc想诅。主要有兩個實現(xiàn):StandaloneMockMvcBuilderDefaultMockMvcBuilder,分別對應(yīng)兩種測試方式,即獨立安裝和繼承web環(huán)境測試(并不會集成真正的web環(huán)境岛心,而是通過相應(yīng)的Mock API進(jìn)行模擬測試来破,無需啟動服務(wù))。MockMvcBuilders提供了對應(yīng)的創(chuàng)建方法standaloneSetup 方法和webAppContextSetup方法,在使用時直接調(diào)用即可忘古。

private MockMvc mockMvc;

@Autowire
private WebApplicationContext webApplicationContext;

@Before
public void setup() {
    /*實例化方式一*/
    mockMvc = MockMvcBuilders.standaloneSetup(new UserController()).build();
    /*實例化方式二*/
    mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}

單元測試方法:

  @Test
    public void testHello() throws Exception {
/*
        1.mockMvc.perform 執(zhí)行一個請求
        2.MockMvcRequestBuilders.get("XXX")構(gòu)造一個請求
        3.ResultActions.param()
        4.ResultActions.accept()
        5.ResultActions.andExpect
        6.ResultActions.andDo 添加一個結(jié)果處理器讳癌,表示要對結(jié)果做點什么事情。
        7.ResultActions.andReturn 表示執(zhí)行完成后存皂,返回響應(yīng)的結(jié)果晌坤。
*/
        mockMvc.perform(MockMvcRequestBuilders.get("/mock-mvc/test-get")
                /*設(shè)置返回類型為utf-8,否則默認(rèn)為ISO-8859-1*/
                .accept(MediaType.APPLICATION_JSON_UTF8_VALUE)
                .param("name","tom"))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().string("hello"))
                .andDo(MockMvcResultHandlers.print());
    }

整個過程如下
1.準(zhǔn)備測試環(huán)境
2.通過MockMvc執(zhí)行請求
3.添加驗證斷言
4.添加結(jié)果處理器
5.得到MvcResult進(jìn)行分自定義斷言/進(jìn)行下一步異步請求
6.卸載測試環(huán)境

注意事項:如果使用DefaultMockMvcBuilder進(jìn)行MockMvc實例化時需在SpringBoot啟動類上添加組件掃描的package的指定,否則會出現(xiàn)404

@ComponentScan(basePackages = "com.creators")

相關(guān)API
RequestBuilder提供了一個方法buildRequest(ServletContext servletContext)用于構(gòu)建MockHttpServletRequest旦袋;其中有兩個子類MockHttpServletRequestBuilder和MockMultipartHttpServletRequestBuilder(文件上傳使用) 骤菠。

MockMvcRequestBuilders提供get、post等多種方法用來實例化RequestBuilder疤孕。

ResultActions,MockMvc.perform(RequestBuilder requestBuilder)的返回值商乎,提供三種能力:andExpect 添加斷言判斷結(jié)果是否達(dá)到預(yù)期;andDo,添加結(jié)果處理器祭阀,比如示例中的打印鹉戚。andReturn返回驗證成功后的MvcResult,用于自定義驗證/下一步的異步處理鲜戒。

一些常用的測試
測試普通控制器

mockMvc.perform(MockMvcRequestBuilders.get("/user/{id}",1))
        /*驗證存儲模型數(shù)據(jù)*/
       .andExpect(MockMvcResultMatchers.model().attributeExists("user"))
        /*驗證viewName*/
       .andExpect(MockMvcResultMatchers.view().name("user/view"))
        /*驗證視圖渲染時forward到的jsp*/
       .andExpect(MockMvcResultMatchers.forwardedUrl("/WEB-INF/jsp/user/view/jsp"))
        /*驗證狀態(tài)碼*/
       .andExpect(MockMvcResultMatchers.status().isOk())
        /*輸出MvcResult到控制臺*/
       .andDo(MockMvcResultHandlers.print());

得到MvcResult自定義驗證

  MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get( "/user/{id}",1)) 
        .andReturn();
  Assert.assertNotNull(result.getModelAndView().getModel().get("user"));

驗證請求參數(shù)綁定到模型數(shù)據(jù)及flash屬性

mockMvc.perform(MockMvcRequestBuilders.post("/user").param("name","wang"))
                /*驗證執(zhí)行控制器類*/
                .andExpect(MockMvcResultMatchers.handler().handlerType(UserController.class))
                /*驗證執(zhí)行控制器方法名*/
                .andExpect(MockMvcResultMatchers.handler().methodName("create"))
                /*驗證頁面沒有錯誤*/
                .andExpect(MockMvcResultMatchers.model().hasNoErrors())
                /*驗證存在flash屬性*/
                .andExpect(MockMvcResultMatchers.flash().attributeExists("success"))
                /*驗證視圖名稱*/
                .andExpect(MockMvcResultMatchers.view().name("redirect:/user"));

文件上傳

 byte[] bytes = new byte[]{1,2};
        mockMvc.perform(MockMvcRequestBuilders.multipart("/user/{id}/icon",1L).file("icon",bytes))
                .andExpect(MockMvcResultMatchers.model().attribute("icon",bytes))
                .andExpect(MockMvcResultMatchers.view().name("success"));

JSON請求/響應(yīng)驗證

String requestBody = "{\"id\":1,\"name\":\"wang\"}";
        mockMvc.perform(MockMvcRequestBuilders.post("/user")
                .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
                .content(requestBody)
                .accept(MediaType.APPLICATION_JSON))
            .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
            /*檢查返回JSON數(shù)據(jù)中某個值的內(nèi)容: 請參考http://goessner.net/articles/JsonPath/*/
            .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1));

        String errorBody = "{id:1,name:wang}";
        MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/user")
                .contentType(MediaType.APPLICATION_JSON).content(errorBody)
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(MockMvcResultMatchers.status().isBadRequest())
                .andReturn();
        Assert.assertTrue(HttpMessageNotReadableException.class.isAssignableFrom(mvcResult.getResolvedException().getClass()));

異步測試

  MvcResult mvcResult1 = mockMvc.perform(MockMvcRequestBuilders.get("/user/async?id=1&name=wang"))
                .andExpect(MockMvcResultMatchers.request().asyncStarted())
                .andExpect(MockMvcResultMatchers.request().asyncResult(CoreMatchers.instanceOf(User.class)))
                .andReturn();
        mockMvc.perform(MockMvcRequestBuilders.asyncDispatch(mvcResult1))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(1));

使用MultiValueMap構(gòu)建參數(shù)

 MultiValueMap<String,String> params = new LinkedMultiValueMap<>();
        params.add("name","wang");
        params.add("hobby","sleep");
        params.add("hobby","eat");
        mockMvc.perform(MockMvcRequestBuilders.post("/user").params(params));

模擬session和cookie

 mockMvc.perform(MockMvcRequestBuilders.get("/index").sessionAttr("name", "value"));
        mockMvc.perform(MockMvcRequestBuilders.get("/index").cookie(new Cookie("name", "value")));

全局配置

  mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
                .defaultRequest(MockMvcRequestBuilders.get("/user/1").requestAttr("default",true))
                .alwaysDo(MockMvcResultHandlers.print())
                .alwaysExpect(MockMvcResultMatchers.request().attribute("default",true))
                .build();

如果是測試Service層代碼 可以在單元測試方法上加上@Transactional注解,在測試完畢后抹凳,數(shù)據(jù)能自動回滾遏餐。
本節(jié)全部源碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市赢底,隨后出現(xiàn)的幾起案子失都,更是在濱河造成了極大的恐慌,老刑警劉巖幸冻,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粹庞,死亡現(xiàn)場離奇詭異,居然都是意外死亡洽损,警方通過查閱死者的電腦和手機庞溜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碑定,“玉大人流码,你說我怎么就攤上這事〔桓担” “怎么了旅掂?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長访娶。 經(jīng)常有香客問我商虐,道長,這世上最難降的妖魔是什么崖疤? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任秘车,我火速辦了婚禮,結(jié)果婚禮上劫哼,老公的妹妹穿的比我還像新娘叮趴。我一直安慰自己,他們只是感情好权烧,可當(dāng)我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布眯亦。 她就那樣靜靜地躺著,像睡著了一般般码。 火紅的嫁衣襯著肌膚如雪妻率。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天板祝,我揣著相機與錄音宫静,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛孤里,可吹牛的內(nèi)容都是我干的伏伯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼捌袜,長吁一口氣:“原來是場噩夢啊……” “哼说搅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起琢蛤,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蜓堕,失蹤者是張志新(化名)和其女友劉穎抛虏,沒想到半個月后博其,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡迂猴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年慕淡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沸毁。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡峰髓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出息尺,到底是詐尸還是另有隱情携兵,我是刑警寧澤,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布搂誉,位于F島的核電站徐紧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏炭懊。R本人自食惡果不足惜并级,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望侮腹。 院中可真熱鬧嘲碧,春花似錦、人聲如沸父阻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽加矛。三九已至履婉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間荒椭,已是汗流浹背谐鼎。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人狸棍。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓身害,卻偏偏與公主長得像,于是被迫代替她去往敵國和親草戈。 傳聞我的和親對象是個殘疾皇子塌鸯,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內(nèi)容