Android單元測試(一)

本文介紹了Android單元測試入門所需了解的內(nèi)容,包括JUnit、Mockito和PowerMock的使用,怎樣寫MVP中的P層的單元測試以及RxJava相關(guān)測試。

本文首發(fā):http://yuweiguocn.github.io/
新浪微博:@于衛(wèi)國

《望岳》
岱宗夫如何?齊魯青未了。
造化鐘神秀,陰陽割昏曉。
蕩胸生層云,決眥入歸鳥。
會(huì)當(dāng)凌絕頂,一覽眾山小。
-唐,杜甫

前言

最近剛學(xué)習(xí)了下Android單元測試,看了小創(chuàng)作的中文系列教程,也看了官方的英文教程,剛接觸那幾天那叫一個(gè)痛苦,簡直就是陷入了泥潭,接觸到了好多框架,并且不清楚什么情況下到底用哪個(gè)框架。我們先從最基礎(chǔ)的做起,項(xiàng)目目前使用的是MVP架構(gòu),先從P層開始做單元測試,由淺入深。之前老說MVP方便單元測試,到底哪方便了,等真開始做單元測試的時(shí)候,才真正開始理解MVP架構(gòu)。首先是我們的P層是不引入任何Android框架代碼的(如果引用了Android框架的代碼需要引入其它框架才能做測試),之前理解不夠,在P層執(zhí)行Toast(應(yīng)該在View層執(zhí)行)操作了。

我們先來看下需要了解的框架:

  • JUnit:Java 編程語言的單元測試框架,主要用于斷言。
  • Mockito:Java界使用最廣泛的一個(gè)mock框架,mock表示在測試環(huán)境中創(chuàng)建一個(gè)類的虛假對(duì)象方便用于驗(yàn)證。Mockito不支持mock匿名類、final類、靜態(tài)方法和private方法。
  • PowerMock:擴(kuò)展了Mockito,支持Mock靜態(tài)、final、私有方法等。

JUnit

倉庫地址:https://github.com/junit-team/junit4

添加依賴:

testCompile 'junit:junit:4.12'

Assert類中比較重要的方法如下:

序號(hào) 方法和描述
1 void assertEquals(boolean expected, boolean actual) 檢查兩個(gè)變量或者等式是否平衡
2 void assertFalse(boolean condition) 檢查條件是假的
3 void assertNotNull(Object object) 檢查對(duì)象不是空的
4 void assertNull(Object object) 檢查對(duì)象是空的
5 void assertTrue(boolean condition) 檢查條件為真
6 void fail() 在沒有報(bào)告的情況下使測試不通過

注意:上面的每一個(gè)方法,都有一個(gè)重載的方法,可以在前面加一個(gè)String類型的參數(shù),表示如果驗(yàn)證失敗的話,將用這個(gè)字符串作為失敗的結(jié)果報(bào)告。

JUnit 中的注解及含義:

序號(hào) 注解和描述
1 @Test 這個(gè)注釋說明依附在 JUnit 的 public void 方法可以作為一個(gè)測試案例。
2 @Before 有些測試在運(yùn)行前需要?jiǎng)?chuàng)造幾個(gè)相似的對(duì)象。在 public void 方法加該注釋是因?yàn)樵摲椒ㄐ枰?test 方法前運(yùn)行。
3 @After 如果你將外部資源在 Before 方法中分配,那么你需要在測試運(yùn)行后釋放他們。在 public void 方法加該注釋是因?yàn)樵摲椒ㄐ枰?test 方法后運(yùn)行。
4 @BeforeClass 在 public void 方法加該注釋是因?yàn)樵摲椒ㄐ枰陬愔兴蟹椒ㄇ斑\(yùn)行。被此注解修飾的方法必須是靜態(tài)的。
5 @AfterClass 它將會(huì)使方法在所有測試結(jié)束后執(zhí)行。這個(gè)可以用來進(jìn)行清理活動(dòng)。 被此注解修飾的方法必須是靜態(tài)的。
6 @Ignore 這個(gè)注釋是用來忽略有關(guān)不需要執(zhí)行的測試的。

超時(shí)

Junit 提供了一個(gè)指定超時(shí)參數(shù)。如果一個(gè)測試用例執(zhí)行的毫秒數(shù)超過了指定的參數(shù)值,那么 Junit 將自動(dòng)將它標(biāo)記為失敗。

@Test(timeout=1000)
public void testPrintMessage() {
    ...
}

捕獲異常

Junit 提供了一個(gè)捕獲異常的參數(shù)。你可以測試代碼是否拋出了預(yù)期的異常。

參數(shù)化

Junit 4 引入了一個(gè)新的功能參數(shù)化測試。參數(shù)化測試允許開發(fā)人員使用不同的值反復(fù)運(yùn)行同一個(gè)測試。你可以遵循下面的步驟來創(chuàng)建參數(shù)化測試。

  • 在測試類上添加注解@RunWith(Parameterized.class)。
  • 創(chuàng)建一個(gè)由 @Parameters 注解的公共的靜態(tài)方法,它返回一個(gè)對(duì)象的集合(數(shù)組)來作為測試數(shù)據(jù)集合。
  • 創(chuàng)建一個(gè)公共的構(gòu)造函數(shù),參數(shù)個(gè)數(shù)和類型與提供的數(shù)據(jù)集合一一對(duì)應(yīng)。
  • 為每一列測試數(shù)據(jù)創(chuàng)建一個(gè)實(shí)例變量。
  • 用實(shí)例變量作為測試數(shù)據(jù)的來源來創(chuàng)建你的測試用例。
public class PrimeNumberChecker {
   public Boolean validate(final Integer primeNumber) {
      for (int i = 2; i < (primeNumber / 2); i++) {
         if (primeNumber % i == 0) {
            return false;
         }
      }
      return true;
   }
}


import java.util.Arrays;
import java.util.Collection;

import org.junit.Test;
import org.junit.Before;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;

@RunWith(Parameterized.class)
public class PrimeNumberCheckerTest {
   private Integer inputNumber;
   private Boolean expectedResult;
   private PrimeNumberChecker primeNumberChecker;

   @Before
   public void initialize() {
      primeNumberChecker = new PrimeNumberChecker();
   }

   // Each parameter should be placed as an argument here
   // Every time runner triggers, it will pass the arguments
   // from parameters we defined in primeNumbers() method
   public PrimeNumberCheckerTest(Integer inputNumber,
      Boolean expectedResult) {
      this.inputNumber = inputNumber;
      this.expectedResult = expectedResult;
   }

   @Parameterized.Parameters
   public static Collection primeNumbers() {
      return Arrays.asList(new Object[][] {
         { 2, true },
         { 6, false },
         { 19, true },
         { 22, false },
         { 23, true }
      });
   }

   // This test will run 4 times since we have 5 parameters defined
   @Test
   public void testPrimeNumberChecker() {
      System.out.println("Parameterized Number is : " + inputNumber);
      assertEquals(expectedResult,
      primeNumberChecker.validate(inputNumber));
   }
}

運(yùn)行結(jié)果:

Mockito

所謂的mock就是創(chuàng)建一個(gè)類的虛假的對(duì)象,在測試環(huán)境中,用來替換掉真實(shí)的對(duì)象,以達(dá)到兩大目的:

  • 驗(yàn)證這個(gè)對(duì)象的某些方法的調(diào)用情況,調(diào)用了多少次,參數(shù)是什么等等
  • 指定這個(gè)對(duì)象的某些方法的行為,返回特定的值,或者是執(zhí)行特定的動(dòng)作

注意:Mockito不支持mock匿名類、final類、靜態(tài)方法和private方法。

添加依賴:

 repositories {
   jcenter()
 }
 dependencies {
   testCompile "org.mockito:mockito-core:+"
 }

驗(yàn)證行為

//靜態(tài)導(dǎo)入
 import static org.mockito.Mockito.*;

 //創(chuàng)建mock
 List mockedList = mock(List.class);

 //使用mock對(duì)象
 mockedList.add("one");
 mockedList.clear();

 //驗(yàn)證
 verify(mockedList).add("one");
 verify(mockedList).clear();

stubbing

//你可以mock一個(gè)實(shí)體類
LinkedList mockedList = mock(LinkedList.class);

//處理指定行為
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());

//將會(huì)打印"first"
System.out.println(mockedList.get(0));

//將會(huì)拋出runtime exception
System.out.println(mockedList.get(1));

//將會(huì)打印"null" 因?yàn)間et(999)沒有指定行為
System.out.println(mockedList.get(999));

//盡管可以驗(yàn)證一個(gè)指定行為的調(diào)用,但通常這是多余的
//如果你關(guān)心get(0)的返回,那會(huì)被其它事情打斷
//如果你不關(guān)心get(0)的返回,那你不應(yīng)該指定行為,直接驗(yàn)證就可以了
verify(mockedList).get(0);
  • 默認(rèn)情況下,所有方法都會(huì)有返回值,一個(gè) mock 將返回 null,一個(gè)原始/基本類型的包裝值或適當(dāng)?shù)目占@?,?duì)于一個(gè) int/Integer 就是 0,而對(duì)于 boolean/Boolean 就是 false。
  • Stubbing 可以被覆蓋。
  • 一旦 stub,該方法將始終返回一個(gè) stub 的值,無論它被調(diào)用多少次。
  • stubbing 的順序是重要的。

參數(shù)匹配器

Mockito 驗(yàn)證參數(shù)值使用 Java 方式:通過使用 equals() 方法。有時(shí),當(dāng)需要額外的靈活性,可以使用參數(shù)匹配器:

//stubbing using built-in anyInt() argument matcher
when(mockedList.get(anyInt())).thenReturn("element");

//stubbing using custom matcher (let's say isValid() returns your own matcher implementation):
when(mockedList.contains(argThat(isValid()))).thenReturn("element");

//following prints "element"
System.out.println(mockedList.get(999));

//you can also verify using an argument matcher
verify(mockedList).get(anyInt());

參數(shù)匹配器允許靈活的驗(yàn)證或 stubbing。自定義參數(shù)的匹配信息,請(qǐng)查看 Javadoc 中 ArgumentMatcher 類。如果你正在使用參數(shù)的匹配,所有的參數(shù)都由匹配器來提供。
下面的示例演示驗(yàn)證,但同樣適用于 stubbing:

verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
//above is correct - eq() is also an argument matcher

verify(mock).someMethod(anyInt(), anyString(), "third argument");
//above is incorrect - exception will be thrown because third argument is given without an argument matcher.

驗(yàn)證調(diào)用次數(shù)

//using mock
mockedList.add("once");

mockedList.add("twice");
mockedList.add("twice");

mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");

//following two verifications work exactly the same - times(1) is used by default
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");

//exact number of invocations verification
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");

//verification using never(). never() is an alias to times(0)
verify(mockedList, never()).add("never happened");

//verification using atLeast()/atMost()
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("five times");
verify(mockedList, atMost(5)).add("three times");

times(1) 是默認(rèn)的,因此,使用的 times(1) 可以顯示的省略。

Stubbing void 方法處理異常

doThrow(new RuntimeException()).when(mockedList).clear();

//following throws RuntimeException:
mockedList.clear();

有序的驗(yàn)證

// A. Single mock whose methods must be invoked in a particular order
List singleMock = mock(List.class);

//using a single mock
singleMock.add("was added first");
singleMock.add("was added second");

//create an inOrder verifier for a single mock
InOrder inOrder = inOrder(singleMock);

//following will make sure that add is first called with "was added first, then with "was added second"
inOrder.verify(singleMock).add("was added first");
inOrder.verify(singleMock).add("was added second");

// B. Multiple mocks that must be used in a particular order
List firstMock = mock(List.class);
List secondMock = mock(List.class);

//using mocks
firstMock.add("was called first");
secondMock.add("was called second");

//create inOrder object passing any mocks that need to be verified in order
InOrder inOrder = inOrder(firstMock, secondMock);

//following will make sure that firstMock was called before secondMock
inOrder.verify(firstMock).add("was called first");
inOrder.verify(secondMock).add("was called second");

// Oh, and A + B can be mixed together at will

有序驗(yàn)證是為了靈活,你不必一個(gè)接一個(gè)驗(yàn)證所有的交互。
此外,您還可以通過創(chuàng)建 InOrder 對(duì)象傳遞只與有序驗(yàn)證相關(guān)的 mock 。

驗(yàn)證 mock 上不會(huì)發(fā)生交互

//using mocks - only mockOne is interacted
mockOne.add("one");

//ordinary verification
verify(mockOne).add("one");

//verify that method was never called on a mock
verify(mockOne, never()).add("two");

//verify that other mocks were not interacted
verifyZeroInteractions(mockTwo, mockThree);

尋找多余的調(diào)用

//using mocks
mockedList.add("one");
mockedList.add("two");

verify(mockedList).add("one");

//following verification will fail
verifyNoMoreInteractions(mockedList);

注意:不建議 verifyNoMoreInteractions() 在每個(gè)測試方法中使用。 verifyNoMoreInteractions() 是從交互測試工具包一個(gè)方便的斷言。只有與它的相關(guān)時(shí)才使用它。濫用它導(dǎo)致難以維護(hù)。

標(biāo)準(zhǔn)創(chuàng)建 mock 方式——使用 @Mock 注解

  • 最小化可重用 mock 創(chuàng)建代碼
  • 使測試類更加可讀性
  • 使驗(yàn)證錯(cuò)誤更加易讀,因?yàn)樽侄蚊Q用于唯一識(shí)別 mock
public class ArticleManagerTest {
 @Mock
 private ArticleCalculator calculator;
 @Mock
 private ArticleDatabase database;
 @Mock
 private UserProvider userProvider;

 private ArticleManager manager;

 @Before
 public void init(){
    MockitoAnnotations.initMocks(this);
 }

 ...

可以使用內(nèi)建 runner: MockitoJUnitRunner 或者 rule: MockitoRule
更多詳見 MockitoAnnotations
另外也可以通過在類上使用@RunWith(MockitoJUnitRunner.class)來達(dá)到相同的效果。

Stubbing 連續(xù)調(diào)用(迭代器式的 stubbing)

when(mock.someMethod("some arg"))
.thenThrow(new RuntimeException())
.thenReturn("foo");

//First call: throws runtime exception:
mock.someMethod("some arg");

//Second call: prints "foo"
System.out.println(mock.someMethod("some arg"));

//Any consecutive call: prints "foo" as well (last stubbing wins).
System.out.println(mock.someMethod("some arg"));


下面是一個(gè)精簡版本:
when(mock.someMethod("some arg"))
.thenReturn("one", "two", "three");

回調(diào) Stubbing

Mockito允許使用泛型 Answer 接口。我們建議您只使用thenReturn() 或 thenThrow() 來 stubbing 。但是,如果你有一個(gè)需要 stub 到泛型 Answer 接口,這里有一個(gè)例子:

when(mock.someMethod(anyString())).thenAnswer(new Answer() {
   Object answer(InvocationOnMock invocation) {
       Object[] args = invocation.getArguments();
       Object mock = invocation.getMock();
       return "called with arguments: " + args;
   }
});

//the following prints "called with arguments: foo"
System.out.println(mock.someMethod("foo"));

doThrow() 家族方法

Stubbing void 方法,需要不同的 when(Object)方法,因?yàn)榫幾g器不喜歡括號(hào)內(nèi)無效的方法。在用于 Stubbing void 方法中,doThrow(Throwable…) 代替了 stubVoid(Object)。主要原因是提高可讀性和與 doAnswer() 保持一致性。
當(dāng)你想用 stub void 方法時(shí),使用 doThrow():

doThrow(new RuntimeException()).when(mockedList).clear();

//following throws RuntimeException:
mockedList.clear();

在調(diào)用 when() 的相應(yīng)地方可以使用 doThrow(), doAnswer(), doNothing(), doReturn() 和 doCallRealMethod(),當(dāng)stub void 方法和stub 方法在 spy 對(duì)象時(shí)(見下面),可以不止一次的 stub 相同的方法,在測試的中期來改變 mock 的行為。在所有的 stubbing 調(diào)用時(shí),你會(huì)更加傾向于使用這些方法來代替 when()。

spy

spy可以實(shí)現(xiàn)調(diào)用對(duì)象的默認(rèn)實(shí)現(xiàn)。
如果不指定mock方法的特定行為,一個(gè)mock對(duì)象的所有非void方法都將返回默認(rèn)值:int、long類型方法將返回0,boolean方法將返回false,對(duì)象方法將返回null等等;而void方法將什么都不做。

區(qū)別:spy對(duì)象的方法默認(rèn)調(diào)用真實(shí)的邏輯,mock對(duì)象的方法默認(rèn)什么都不做,或直接返回默認(rèn)值。

//假設(shè)目標(biāo)類的實(shí)現(xiàn)是這樣的
public class PasswordValidator {
    public boolean verifyPassword(String password) {
        return "xiaochuang_is_handsome".equals(password);
    }
}

@Test
public void testSpy() {
    //跟創(chuàng)建mock類似,只不過調(diào)用的是spy方法,而不是mock方法。spy的用法
    PasswordValidator spyValidator = Mockito.spy(PasswordValidator.class);

    //在默認(rèn)情況下,spy對(duì)象會(huì)調(diào)用這個(gè)類的真實(shí)邏輯,并返回相應(yīng)的返回值,這可以對(duì)照上面的真實(shí)邏輯
    spyValidator.verifyPassword("xiaochuang_is_handsome"); //true
    spyValidator.verifyPassword("xiaochuang_is_not_handsome"); //false

    //spy對(duì)象的方法也可以指定特定的行為
    Mockito.when(spyValidator.verifyPassword(anyString())).thenReturn(true);

    //同樣的,可以驗(yàn)證spy對(duì)象的方法調(diào)用情況
    spyValidator.verifyPassword("xiaochuang_is_handsome");
    Mockito.verify(spyValidator).verifyPassword("xiaochuang_is_handsome"); //pass
}

捕獲參數(shù)

ArgumentCaptor類允許我們?cè)趘erification期間訪問方法的參數(shù)。得到方法的參數(shù)后我們可以使用它進(jìn)行測試。

@Captor
ArgumentCaptor<List> arguments;

@Before
public void init() {
    // Mockito has a very convenient way to inject mocks by using the @Mock annotation. To
    // inject the mocks in the test the initMocks method needs to be called.
    MockitoAnnotations.initMocks(this);
}

@Test
public void testArgument() {
    List asList = Arrays.asList("someElement_test", "someElement");
    final List mockedList = mock(List.class);
    mockedList.addAll(asList);

    verify(mockedList).addAll(arguments.capture());
    final List capturedArgument = arguments.getValue();
    assertEquals(capturedArgument.get(1),"someElement");
}

PowerMock

倉庫地址:https://github.com/powermock/powermock

//添加依賴
testCompile "org.powermock:powermock-module-junit4:1.6.4"
testCompile "org.powermock:powermock-module-junit4-rule:1.6.4"
testCompile "org.powermock:powermock-api-mockito:1.6.4"
testCompile "org.powermock:powermock-classloading-xstream:1.6.4"

PowerMock擴(kuò)展了EasyMock和Mockito框架,增加了對(duì)static和final方法mock支持等功能。
PowerMock有兩個(gè)重要的注解:

  • @RunWith(PowerMockRunner.class)
  • @PrepareForTest( { YourClassWithEgStaticMethod.class })

如果你的測試用例里沒有使用注解@PrepareForTest,那么可以不用加注解@RunWith(PowerMockRunner.class),反之亦然。當(dāng)你需要使用PowerMock強(qiáng)大功能(Mock靜態(tài)、final、私有方法等)的時(shí)候,就需要加注解@PrepareForTest。

普通Mock: Mock參數(shù)傳遞的對(duì)象

public boolean callArgumentInstance(File file) {
     return file.exists();
}

@Test
public void testCallArgumentInstance() {
    File file = PowerMockito.mock(File.class);
    ClassUnderTest underTest = new ClassUnderTest();
    PowerMockito.when(file.exists()).thenReturn(true);
    Assert.assertTrue(underTest.callArgumentInstance(file));
}

說明:普通Mock不需要加@RunWith和@PrepareForTest注解。

Mock方法內(nèi)部new出來的對(duì)象

public class ClassUnderTest {
    public boolean callInternalInstance(String path) {
        File file = new File(path);
        return file.exists();
    }
}

@RunWith(PowerMockRunner.class)
public class TestClassUnderTest {
    @Test
    @PrepareForTest(ClassUnderTest.class)
    public void testCallInternalInstance() throws Exception {
        File file = PowerMockito.mock(File.class);
        ClassUnderTest underTest = new ClassUnderTest();
        PowerMockito.whenNew(File.class).withArguments("bbb").thenReturn(file);
        PowerMockito.when(file.exists()).thenReturn(true);
        Assert.assertTrue(underTest.callInternalInstance("bbb"));
    }
}

說明:當(dāng)使用PowerMockito.whenNew方法時(shí),必須加注解@PrepareForTest和@RunWith。注解@PrepareForTest里寫的類是需要mock的new對(duì)象代碼所在的類。

Mock普通對(duì)象的final方法

public class ClassUnderTest {
    public boolean callFinalMethod(ClassDependency refer) {
        return refer.isAlive();
    }
}
public class ClassDependency {  
    public final boolean isAlive() {
        // do something
        return false;
    }
}
@RunWith(PowerMockRunner.class)
public class TestClassUnderTest {
    @Test
    @PrepareForTest(ClassDependency.class)
    public void testCallFinalMethod() {
        ClassDependency depencency =  PowerMockito.mock(ClassDependency.class);
        ClassUnderTest underTest = new ClassUnderTest();
        PowerMockito.when(depencency.isAlive()).thenReturn(true);
        Assert.assertTrue(underTest.callFinalMethod(depencency));
    }
}

說明: 當(dāng)需要mock final方法的時(shí)候,必須加注解@PrepareForTest和@RunWith。注解@PrepareForTest里寫的類是final方法所在的類。

Mock普通類的靜態(tài)方法

public class ClassUnderTest {
    public boolean callStaticMethod() {
        return ClassDependency.isExist();
    }  
}
public class ClassDependency {
    public static boolean isExist() {
        // do something
        return false;
    }
}

@RunWith(PowerMockRunner.class)
public class TestClassUnderTest {
    @Test
    @PrepareForTest(ClassDependency.class)
    public void testCallStaticMethod() {
        ClassUnderTest underTest = new ClassUnderTest();
        PowerMockito.mockStatic(ClassDependency.class);
        PowerMockito.when(ClassDependency.isExist()).thenReturn(true);
        Assert.assertTrue(underTest.callStaticMethod());
    }
}

說明:當(dāng)需要mock靜態(tài)方法的時(shí)候,必須加注解@PrepareForTest和@RunWith。注解@PrepareForTest里寫的類是靜態(tài)方法所在的類。

Mock RxJava

@RunWith(PowerMockRunner.class)
@PrepareForTest(AppApi.class)
public class SubmitPresenterTest {
    @Mock
    private SubmitContract.View mView;
    private SubmitContract.Presenter mPresenter;


    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
        mPresenter = new SubmitPresenter(mView);
    }

    @Test
    public void testLoadDataSuccess() {
        mockStatic(AppApi.class);
        //Observable.just(new InsuranceResultBean())會(huì)調(diào)用onNext方法
        when(AppApi.getSubmitInsuranceResult(any(InsuranceInfoBean.class))).thenReturn(Observable.just(new InsuranceResultBean()));
        //Observable.<InsuranceResultBean>empty() 會(huì)直接調(diào)用onCompleted()方法
        when(AppApi.getSubmitInsuranceResult(any(InsuranceInfoBean.class))).thenReturn(Observable.<InsuranceResultBean>empty());
        mPresenter.submit(new InsuranceInfoBean());
        verify(mView).onSuccess(any(InsuranceResultBean.class));
    }


    @Test
    public void testLoadDataFail() {
        mockStatic(AppApi.class);
        //Observable.<InsuranceResultBean>error(new BusinessException(-3, "test fail"))會(huì)調(diào)用onError方法
        when(AppApi.getSubmitInsuranceResult(any(InsuranceInfoBean.class))).thenReturn(Observable.<InsuranceResultBean>error(new BusinessException(-3, "test fail")));
        mPresenter.submit(new InsuranceInfoBean());
        verify(mView).showVerifyDialog();
    }

    @Test
    public void testLoadDataFailToast() {
        mockStatic(AppApi.class);
        when(AppApi.getSubmitInsuranceResult(any(InsuranceInfoBean.class))).thenReturn(Observable.<InsuranceResultBean>error(new BusinessException(-4, "test fail")));
        mPresenter.submit(new InsuranceInfoBean());
        verify(mView).showVerifyDialog();
        verify(mView).onToast(any(String.class));
    }
}

Mock 私有方法

public class ClassUnderTest {
    public boolean callPrivateMethod() {
        return isExist();
    }       
    private boolean isExist() {
        return false;
    }
}

@RunWith(PowerMockRunner.class)
public class TestClassUnderTest {
    @Test
    @PrepareForTest(ClassUnderTest.class)
    public void testCallPrivateMethod() throws Exception {
       ClassUnderTest underTest = PowerMockito.mock(ClassUnderTest.class);
       PowerMockito.when(underTest.callPrivateMethod()).thenCallRealMethod();
       PowerMockito.when(underTest, "isExist").thenReturn(true);
       Assert.assertTrue(underTest.callPrivateMethod());
    }
}

說明:和Mock普通方法一樣,只是需要加注解@PrepareForTest(ClassUnderTest.class),注解里寫的類是私有方法所在的類。

Mock系統(tǒng)類的靜態(tài)和final方法

public class ClassUnderTest {
    public boolean callSystemFinalMethod(String str) {
        return str.isEmpty();
    }
    public String callSystemStaticMethod(String str) {
        return System.getProperty(str);
    }
}

@RunWith(PowerMockRunner.class)
public class TestClassUnderTest {
  @Test
  @PrepareForTest(ClassUnderTest.class)
  public void testCallSystemStaticMethod() {
      ClassUnderTest underTest = new ClassUnderTest();
      PowerMockito.mockStatic(System.class);
      PowerMockito.when(System.getProperty("aaa")).thenReturn("bbb");
      Assert.assertEquals("bbb", underTest.callJDKStaticMethod("aaa"));
  }
}

說明:和Mock普通對(duì)象的靜態(tài)方法、final方法一樣,只不過注解@PrepareForTest里寫的類不一樣 ,注解里寫的類是需要調(diào)用系統(tǒng)方法所在的類。

PowerMock 簡單實(shí)現(xiàn)原理

  • 當(dāng)某個(gè)測試方法被注解@PrepareForTest標(biāo)注以后,在運(yùn)行測試用例時(shí),會(huì)創(chuàng)建一個(gè)新的org.powermock.core.classloader.MockClassLoader實(shí)例,然后加載該測試用例使用到的類(系統(tǒng)類除外)。
  • PowerMock會(huì)根據(jù)你的mock要求,去修改寫在注解@PrepareForTest里的class文件(當(dāng)前測試類會(huì)自動(dòng)加入注解中),以滿足特殊的mock需求。例如:去除final方法的final標(biāo)識(shí),在靜態(tài)方法的最前面加入自己的虛擬實(shí)現(xiàn)等。
  • 如果需要mock的是系統(tǒng)類的final方法和靜態(tài)方法,PowerMock不會(huì)直接修改系統(tǒng)類的class文件,而是修改調(diào)用系統(tǒng)類的class文件,以滿足mock需求。

測試RxJava

TestSubscriber用于單元測試,你可以用來執(zhí)行斷言,檢查接收的事件,或者包裝一個(gè)被mock的Subscriber。可以通過RxJava提供的Hook的方式修改線程為立即執(zhí)行。

public Observable<String> getTestRxJava() {
    return Observable.create(new Observable.OnSubscribe<String>() {
        @Override
        public void call(Subscriber<? super String> subscriber) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            subscriber.onNext("test");
            subscriber.onCompleted();
        }
    }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}



@Test
public void testSync() {
    //通過Hook的方式修改主線程為immediate
    RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
        @Override
        public Scheduler getMainThreadScheduler() {
            return Schedulers.immediate();
        }
    });
    //通過Hook的方式修改io線程為immediate
    RxJavaPlugins.getInstance().registerSchedulersHook(new RxJavaSchedulersHook() {
        @Override
        public Scheduler getIOScheduler() {
            return Schedulers.immediate();
        }
    });

    TestSubscriber<String> testSubscriber = new TestSubscriber<>();
    mPresenter.getTestRxJava().subscribe(testSubscriber);
    testSubscriber.assertValue("test");
}

圖為TestSubscriber類中提供的方法。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,208評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,746評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,477評(píng)論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,960評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,200評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,726評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,617評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,807評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,049評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評(píng)論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,432評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,769評(píng)論 2 372

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,781評(píng)論 18 139
  • 在博客Android單元測試之Mockito中,主要介紹了Mockito測試框架和基本使用。在博客結(jié)束時(shí),我們提出...
    水木飛雪閱讀 13,715評(píng)論 5 18
  • 在博客Android單元測試之JUnit4中,我們簡單地介紹了:什么是單元測試,為什么要用單元測試,并展示了一個(gè)簡...
    水木飛雪閱讀 9,509評(píng)論 4 18
  • 單元測試 單測定義 單元測試(Unit Testing)又稱為模塊測試, 是針對(duì)程序模塊(軟件設(shè)計(jì)的最小單位)來進(jìn)...
    運(yùn)維開發(fā)筆記閱讀 2,009評(píng)論 0 2
  • 01 從村北回家時(shí),大意的我被一個(gè)來路不明的男人手中的大鐵鉗套住脖子掙脫不開時(shí),我頓時(shí)覺得天都黑了。 村南的大塊頭...
    也許我在等時(shí)光閱讀 910評(píng)論 4 5