本文整理自 B站 UP主 心蓝说Java 汪文君Mockito实战视频笔记
introduction
Mockito is a mocking framework that tastes really good. It lets you write beautiful tests with a clean & simple API. Mockito doesn’t give you hangover because the tests are very readable and they produce clean verification errors. Read more about features & motivations.
significance of mockito
Automated tests are a safety net. They run and notify if the system is broken so that the offending code can be fixed very quickly. If a test suite runs for an hour, the purpose of quick feedback is compromised.
The following are example s of testing-unfriendly behaviors:
Accquiring a database connection and fetching/updating dataConnecting to the Internet and downloading filesInteracting with an SMTP server to send an e-mailLooking up JNDI (Java Naming and Directory Interface])Invoking a web servicePerforming I/O operations, such as printing a report
Mockito plays a key role in mocking external dependencies.
1.**how to mock **@RunWith(MockitoJUnitRunner.class)
@RunWith(MockitoJUnitRunner.class) public class AccountLoginControllerTest { private AccountDao accountDao; private HttpServletRequest request; private AccountLoginController controller; @Before public void setUp() { this.accountDao = Mockito.mock(AccountDao.class); this.request = Mockito.mock(HttpServletRequest.class); this.controller = new AccountLoginController(accountDao); } @Test public void testLoginSuccess() { Account account = new Account(); Mockito.when(request.getParameter("username")).thenReturn("alex"); Mockito.when(request.getParameter("password")).thenReturn("123456"); Mockito.when(accountDao.findAccount(anyString(), anyString())).thenReturn(account); assertThat(controller.login(request), equalTo("index.jsp")); } @Test public void testLoginFailure() { Mockito.when(accountDao.findAccount(anyString(), anyString())).thenReturn(null); assertThat(controller.login(request), equalTo("/login")); } @Test public void testLogin505() { Mockito.when(accountDao.findAccount(anyString(), anyString())).thenThrow(UnsupportedOperationException.class); assertThat(controller.login(request), equalTo("505")); } }
MockitoAnnotations.initMocks(this) and @Mock
public class AccountControllerTest { private AccountLoginController controller; private AccountDao accountDao; @Mock private HttpServletRequest request; @Before public void setUp(){ MockitoAnnotations.initMocks(this); accountDao = Mockito.mock(AccountDao.class); controller = new AccountLoginController(accountDao); } @Test public void testLoginSuccess(){ Mockito.when(accountDao.findAccount(anyString(), anyString())).thenReturn(new Account()); assertThat(controller.login(request), equalTo("index.jsp")); } }
MockitoRule mockRule = MockitoJUnit.rule()
public class AccountControllerMockByRuleTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock(answer = Answers.RETURNS_SMART_NULLS) private AccountDao accountDao; @Test public void testLogin(){ Account account = accountDao.findAccount(anyString(), anyString()); assertNotNull(account); } }
mock 方法内部类
public class DeepMockOrderServiceTest { @Mock private Order order; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private OrderService service; @Before public void setUp() { MockitoAnnotations.initMocks(this); } @Test public void testOrderService(){ // Mockito.when(service.getOrder()).thenReturn(order); // order.foo(); service.getOrder().foo(); } }2.how to stubbing
stubbing void methodstubbing method with exceptionstubbing consecutive calls(iterator-style-stubbing)stubbing with callbacksstubbing with real call
import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.runners.MockitoJUnitRunner; import java.util.ArrayList; import java.util.List; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) public class StubbingTest { private List3.mockito spyinglist; @Before public void init(){ this.list = mock(ArrayList.class); } @Test public void howToUseStubbing(){ when(list.get(0)).thenReturn("first"); assertThat(list.get(0), equalTo("first")); when(list.get(anyInt())).thenThrow(new RuntimeException()); try { list.get(0); fail(); }catch (Exception e){ assertThat(e, instanceOf(RuntimeException.class)); } } @Test public void howToStubbingVoidMethod(){ doNothing().when(list).clear(); list.clear(); // 验证执行次数 verify(list, times(1)).clear(); // 抛出异常 doThrow(RuntimeException.class).when(list).clear(); try { list.clear(); fail(); }catch (Exception e){ assertThat(e, instanceOf(RuntimeException.class)); } } @Test public void stubbingDoReturn(){ when(list.get(0)).thenReturn("first"); doReturn("second").when(list).get(1); assertThat(list.get(0), equalTo("first")); assertThat(list.get(1), equalTo("second")); } @Test public void iterateStubbing(){ when(list.size()).thenReturn(1, 2, 3, 4); // when(list.size()).thenReturn(1).thenReturn(2).thenReturn(3).thenReturn(4); assertThat(list.size(), equalTo(1)); assertThat(list.size(), equalTo(2)); assertThat(list.size(), equalTo(3)); assertThat(list.size(), equalTo(4)); assertThat(list.size(), equalTo(4)); } @Test public void stubbingAnswer(){ when(list.get(anyInt())).thenAnswer(invocationOnMock -> { Integer index = invocationOnMock.getArgumentAt(0, Integer.class); return String.valueOf(index * 10); }); assertThat(list.get(0), equalTo("0")); assertThat(list.get(777), equalTo("7770")); } @Test public void stubbingWithRealMethod(){ StubbingService service = mock(StubbingService.class); when(service.getStr()).thenReturn("alex"); assertThat(service.getStr(), equalTo("alex")); when(service.getInt()).thenCallRealMethod(); assertThat(service.getInt(), equalTo(10)); } @After public void destroy(){ reset(this.list); } } public class StubbingService { public int getInt(){ return 10; } public String getStr(){ throw new RuntimeException(); } }
Spying on real objects can be associated with “partial mocking” concept. Before the release 1.8, Mockito spies were not real partial mocks. The reason was we thought partial mock is a code smell. At some point we found legitimate use cases for partial mocks(3rd party interfaces, interim refactoring of legacy code).
import java.util.ArrayList; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class SpyingTest { @Test public void testSpy(){ ArrayListrealList = new ArrayList<>(); ArrayList spyList = spy(realList); spyList.add("Mockito"); spyList.add("PowerMock"); assertThat(spyList.get(0), equalTo("Mockito")); assertThat(spyList.get(1), equalTo("PowerMock")); assertThat(spyList.isEmpty(), equalTo(false)); when(spyList.isEmpty()).thenReturn(true); when(spyList.size()).thenReturn(0); assertThat(spyList.get(0), equalTo("Mockito")); assertThat(spyList.get(1), equalTo("PowerMock")); assertThat(spyList.isEmpty(), equalTo(true)); assertThat(spyList.size(), equalTo(0)); } }
import org.junit.Before; import org.junit.Test; import org.mockito.MockitoAnnotations; import org.mockito.Spy; import java.util.ArrayList; import java.util.List; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.when; public class SpytingAnnotationTest { @Spy private List4.Mockito Argument Matcherslist = new ArrayList<>(); @Before public void init(){ MockitoAnnotations.initMocks(this); } @Test public void testSpying(){ list.add("Mockito"); list.add("PowerMock"); assertThat(list.get(0), equalTo("Mockito")); assertThat(list.get(1), equalTo("PowerMock")); assertThat(list.isEmpty(), equalTo(false)); when(list.isEmpty()).thenReturn(true); when(list.size()).thenReturn(0); assertThat(list.get(0), equalTo("Mockito")); assertThat(list.get(1), equalTo("PowerMock")); assertThat(list.isEmpty(), equalTo(true)); assertThat(list.size(), equalTo(0)); } }
The argument matcher plays a key role in mocking. Mock objects return expected values, but when need to return different values for different arguemnts, the argument matcher comes into play.
Mockito returns expected values when a method is stubbed. If the method takes arguments, the argument must match during the execution.
Mockito verifies argument values in natural Java style by using an objects’s equals() method.
Sometimes, we use argument matchers when extra flexibility is required. Mockito provides built-in matchers, such as anything(), anyDouble(), anyString(), anyList(), and anyCollection().参考链接
isA(Class clazz), any(Class clazz), eq(primitive value)
import org.junit.Test; import org.mockito.Mockito; import java.util.ArrayList; import java.util.List; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.*; public class ArgumentsMatcher { @Test public void basicTest(){ List5.Working With WildCard Matcherslist = mock(ArrayList.class); when(list.get(0)).thenReturn(100); assertThat(list.get(0), equalTo(100)); assertThat(list.get(1), nullValue()); } @Test public void testComplex(){ Foo foo = mock(Foo.class); when(foo.function(Mockito.isA(Child1.class))).thenReturn(100); int result = foo.function(new Child1()); assertThat(result, equalTo(100)); result = foo.function(new Child2()); assertThat(result, equalTo(0)); reset(foo); when(foo.function(Mockito.any(Child1.class))).thenReturn(200); result = foo.function(new Child2()); assertThat(result, equalTo(200)); } static class Foo{ int function(Parent parent){ return parent.work(); } } interface Parent{ int work(); } class Child1 implements Parent{ @Override public int work() { throw new RuntimeException(); } } class Child2 implements Parent { @Override public int work() { throw new RuntimeException(); } } }
A test invokes a method on a code under test. When the invoke method creates a new object and passess that to a mock object, the test method doesn’t have the reference of that new object. So that test cannot stub the mock method with a specific value, as the value is not available to the test mehod. In this context, we use the wildcard matchers.
anyXXX()
any()
isA()
eq()
etc.
import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; import java.io.Serializable; import java.util.Collections; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) public class WildCardArgumentsMatcherTest { @Mock private SimpleService simpleService; @Test public void testWildcardMethod1(){ Mockito.when(simpleService.method1(anyInt(), anyString(), anyCollection(), isA(Serializable.class))).thenReturn(100); int result = simpleService.method1(1, "he", Collections.emptySet(), "Mockito"); assertThat(result, equalTo(100)); result = simpleService.method1(1, "he", Collections.emptyList(), "MockitoForJava"); assertThat(result, equalTo(100)); } @Test public void wildcardMethodWithSpec(){ // 大范围的在最前面stub when(simpleService.method1(anyInt(), anyString(), anyCollection(), isA(Serializable.class))).thenReturn(-1); // 所有参数全为wildcard,不能部分参数为wildcard // 特定值用eq(), 否则报错 when(simpleService.method1(anyInt(), eq("alex"), anyCollection(), isA(Serializable.class))).thenReturn(100); when(simpleService.method1(anyInt(), eq("he"), anyCollection(), isA(Serializable.class))).thenReturn(200); // specific value int result = simpleService.method1(1, "alex", Collections.emptyList(), "Mockito"); assertThat(result, equalTo(100)); result = simpleService.method1(1, "he", Collections.emptyList(), "Mockito"); assertThat(result, equalTo(200)); // 返回wildcard值 result = simpleService.method1(1, "sdfgbgfd", Collections.emptyList(), "Mockito"); assertThat(result, equalTo(-1)); } @Test public void wildcardMethod2(){ doNothing().when(simpleService).method2(anyInt(), anyString(), anyCollection(), isA(Serializable.class)); simpleService.method2(1, "alex", Collections.emptyList(), "Mockito"); verify(simpleService, times(1)).method2(1, "alex", Collections.emptyList(), "Mockito"); } @After public void destroy(){ // 还原stub reset(simpleService); } }6.Hamcrest matcher
Hamcrest provides a utility matcher class, org.hamcrest.CoreMatchers. A few methods for CoreMatchers include allOf, anyOf, both, either, descibedAs, everyItem, is, isA, anything, hasItem, hasItems, equalTo, any, instanceOf, not, nullValue, notNullValue, sameInstance, end the Instance. It also includes a few string methods such as startsWith, endWith, and containsString. All these methods return a matcher.
import org.junit.Test; import java.util.stream.Stream; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.assertThat; public class AssertMatcherTest { @Test public void matcher01(){ int i = 10; assertThat(i, equalTo(10)); assertThat(i, not(equalTo(20))); assertThat(i, is(10)); assertThat(i, not(is(20))); } @Test public void matcher02(){ double price = 23.45; assertThat(price, either(equalTo(23.45)).or(equalTo(23.54))); assertThat(price, both(equalTo(23.45)).and(not(equalTo(23.53)))); assertThat(price, anyOf(is(23.45), is(33.44), is(333.666), not(55.66))); assertThat(price, allOf(is(23.45), not(is(34.77)), not(88.78))); assertThat(Stream.of(1, 2, 3).anyMatch(i -> i > 2), equalTo(true)); assertThat(Stream.of(1, 2, 3).allMatch(i -> i > 0), equalTo(true)); } @Test public void matcher03(){ double price = 23.45; assertThat("the double value assertion failed", price, either(equalTo(23.54)).or(equalTo(24.35))); } }7.Extend Matcher
import org.hamcrest.baseMatcher; import org.hamcrest.Description; import org.hamcrest.Factory; public class CompareThanextends baseMatcher { private final T value; private final boolean greater; public CompareThan(T value, boolean greater) { this.value = value; this.greater = greater; } @Override public boolean matches(Object actual) { Class> clazz = actual.getClass(); if (clazz == Integer.class) { return greater ? (Integer) actual > (Integer) value : (Integer) actual < (Integer) value; }else if (clazz == Long.class){ return greater ? (Long) actual > (Long) value : (Long) actual < (Long) value; }else if (clazz == Short.class){ return greater ? (Short) actual > (Short) value : (Short) actual < (Short) value; }else if (clazz == Byte.class){ return greater ? (Byte) actual > (Byte) value : (Byte) actual < (Byte) value; }else if (clazz == Float.class){ return greater ? (Float) actual > (Float) value : (Float) actual < (Float) value; }else if (clazz == Double.class){ return greater ? (Double) actual > (Double) value : (Double) actual < (Double) value; }else{ throw new AssertionError("unsupported number type"); } } @Factory public static CompareThan gt(T value){ return new CompareThan<>(value, true); } @Factory public static CompareThan lt(T value){ return new CompareThan<>(value, false); } @Override public void describeTo(Description description) { description.appendText("compare two numbers failed"); } }
import org.junit.Test; import static com.heju.mockito.CompareThan.gt; import static com.heju.mockito.CompareThan.lt; import static org.hamcrest.CoreMatchers.both; import static org.junit.Assert.assertThat; public class MyAssertThatTest { @Test public void ltAndGt(){ assertThat(12, gt(10)); assertThat(12, both(gt(10)).and(lt(13))); } }
refactor code
public interface CompareNumber{ boolean compare(T expected, T actual); } public class CompareNumberImpl implements CompareNumber { private final boolean greater; public CompareNumberImpl(boolean greater) { this.greater = greater; } @Override public boolean compare(T actual, T expected) { Class> clazz = expected.getClass(); if (clazz == Integer.class) { return greater ? (Integer) expected > (Integer) actual : (Integer) expected < (Integer) actual; }else if (clazz == Long.class){ return greater ? (Long) expected > (Long) actual : (Long) expected < (Long) actual; }else if (clazz == Short.class){ return greater ? (Short) expected > (Short) actual : (Short) expected < (Short) actual; }else if (clazz == Byte.class){ return greater ? (Byte) expected > (Byte) actual : (Byte) expected < (Byte) actual; }else if (clazz == Float.class){ return greater ? (Float) expected > (Float) actual : (Float) expected < (Float) actual; }else if (clazz == Double.class){ return greater ? (Double) expected > (Double) actual : (Double) expected < (Double) actual; }else{ throw new AssertionError("unsupported number type"); } } }
import org.hamcrest.baseMatcher; import org.hamcrest.Description; import org.hamcrest.Factory; public class CompareNumberMatcherextends baseMatcher { private final T value; private final CompareNumber COMPARE; public CompareNumberMatcher(T value, boolean greater) { this.value = value; this.COMPARE = new CompareNumberImpl<>(greater); } @Override public boolean matches(Object actual) { return this.COMPARE.compare(value, (T) actual); } @Factory public static CompareNumberMatcher gt(T value){ return new CompareNumberMatcher (value, true); } @Factory public static CompareNumberMatcher lt(T value){ return new CompareNumberMatcher<>(value, false); } @Override public void describeTo(Description description) { description.appendText("compare two numbers failed"); } }
import org.junit.Test; import static org.hamcrest.CoreMatchers.both; import static org.junit.Assert.assertThat; public class MyAssertThatTest { @Test public void compareThan(){ assertThat(12, CompareNumberMatcher.gt(10)); assertThat(12, both(CompareNumberMatcher.gt(10)).and(CompareNumberMatcher.lt(13))); } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)