/* * Copyright (c) 2006 Rogério Liesenfeld * This file is subject to the terms of the MIT license (see LICENSE.txt). */ package mockit; import java.security.cert.*; import java.util.*; import static java.util.Arrays.*; import static java.util.Collections.singletonList; import org.hamcrest.*; import org.junit.*; import org.junit.rules.*; import static org.junit.Assert.*; import mockit.internal.*; import static mockit.ExpectationsWithArgMatchersTest.Delegates.*; import static org.hamcrest.CoreMatchers.*; public final class ExpectationsWithArgMatchersTest { @Rule public final ExpectedException thrown = ExpectedException.none(); @SuppressWarnings("unused") static class Collaborator { void setValue(int value) {} void setValue(double value) {} void setValue(float value) {} void setValue(String value) {} void setValues(char c, boolean b) {} void setValues(String[] values) {} void setTextualValues(Collection<String> values) {} void doSomething(Integer i) {} boolean doSomething(String s) { return false; } List<?> complexOperation(Object input1, Object... otherInputs) { return input1 == null ? Collections.emptyList() : asList(otherInputs); } final void simpleOperation(int a, String b, Date c) {} void setValue(Certificate cert) {} void setValue(Exception ex) {} String useObject(Object arg) { return ""; } } @Mocked Collaborator mock; // "Unexpected invocations" which can only occur with strict expectations ////////////////////////////////////////// @Test public void replayWithUnexpectedMethodArgument() { new StrictExpectations() {{ mock.simpleOperation(2, "test", null); }}; thrown.expect(UnexpectedInvocation.class); thrown.expectMessage(" expected \"test\", got \"other\""); mock.simpleOperation(2, "other", null); } @Test public void replayWithMultipleUnexpectedMethodArguments() { new StrictExpectations() {{ mock.setValues('a', true); }}; thrown.expect(UnexpectedInvocation.class); thrown.expectMessage(" expected \"a\", got \"b\""); mock.setValues('b', false); } @Test public void replayWithUnexpectedSecondMethodArgument() { new StrictExpectations() {{ mock.setValues('a', true); }}; thrown.expect(UnexpectedInvocation.class); thrown.expectMessage(" expected true, got false"); mock.setValues('a', false); } @Test public void replayWithUnexpectedNullArgument() { new StrictExpectations() {{ mock.simpleOperation(2, "test", null); }}; thrown.expect(UnexpectedInvocation.class); thrown.expectMessage("expected \"test\", got null"); mock.simpleOperation(2, null, null); } @Test public void replayWithUnexpectedMethodArgumentUsingMatcher() { new StrictExpectations() {{ mock.setValue(withEqual(-1)); }}; thrown.expect(UnexpectedInvocation.class); thrown.expectMessage("expected -1, got 1"); mock.setValue(1); } @Test public void expectInvocationWithDifferentThanExpectedProxyArgument(@Mocked final Runnable mock2) { new StrictExpectations() {{ mock.complexOperation(mock2); }}; thrown.expect(UnexpectedInvocation.class); thrown.expectMessage("got null"); mock.complexOperation(null); } @Test public void expectInvocationWithEqualFloatArgumentButWithDifferentReplayValue() { new StrictExpectations() {{ mock.setValue(withEqual(3.0F, 0.01)); }}; thrown.expect(UnexpectedInvocation.class); thrown.expectMessage(" within 0.01 of 3.0, got 3.02F"); mock.setValue(3.02F); } @Test public void expectInvocationWithMatchForRegexButWithNonMatchingArgument() { new StrictExpectations() {{ mock.complexOperation(withMatch("test")); }}; thrown.expect(UnexpectedInvocation.class); mock.complexOperation("otherValue"); } @Test public void replayWithDifferentArgumentOfClassLackingEqualsMethod() { new StrictExpectations() {{ mock.setValue(new RuntimeException("Recorded")); }}; thrown.expect(UnexpectedInvocation.class); thrown.expectMessage("argument class java.lang.RuntimeException has no \"equals\" method"); mock.setValue(new RuntimeException("Replayed")); } // Successful strict expectations ////////////////////////////////////////////////////////////////////////////////// @Test public void expectInvocationWithAnyArgumentUsingField() { new StrictExpectations() {{ mock.setValue(anyInt); }}; mock.setValue(3); } @Test public void expectInvocationToPrivateInstanceMethodUsingAnyFieldMatcher() { new StrictExpectations() {{ mock.doSomething(anyInt); }}; mock.doSomething(3); } @Test public void expectInvocationWithAnyArgumentUsingMethod() { new StrictExpectations() {{ mock.setValue(withAny(1)); }}; mock.setValue(3); } @Test public void expectInvocationWithEqualArgument() { new StrictExpectations() {{ mock.setValue(withEqual(3)); }}; mock.setValue(3); } @Test public void expectInvocationWithEqualArrayArgument() { new StrictExpectations() {{ mock.setValues(withEqual(new String[] {"A", "bb", "cee"})); }}; mock.setValues(new String[] {"A", "bb", "cee"}); } @Test public void expectInvocationWithEqualDoubleArgument() { new StrictExpectations() {{ mock.setValue(withEqual(3.0, 0.01)); times = 3; }}; mock.setValue(3.0); mock.setValue(3.01); mock.setValue(2.99); } @Test public void expectInvocationWithEqualFloatArgument() { new StrictExpectations() {{ mock.setValue(withEqual(3.0F, 0.01)); times = 3; }}; mock.setValue(3.0F); mock.setValue(3.01F); mock.setValue(2.99F); } @Test public void expectInvocationWithNotEqualArgument() { new StrictExpectations() {{ mock.setValue(withNotEqual(3)); }}; mock.setValue(4); } @Test public void expectInvocationWithInstanceOfClassFromGivenObject() { new StrictExpectations() {{ mock.complexOperation("string"); mock.complexOperation(withInstanceLike("string")); }}; mock.complexOperation("string"); mock.complexOperation("another string"); } @Test public void expectInvocationWithInstanceOfGivenClass() { new StrictExpectations() {{ mock.complexOperation(withInstanceOf(long.class)); }}; mock.complexOperation(5L); } @Test public void expectInvocationWithNullArgument() { new StrictExpectations() {{ mock.complexOperation(withNull()); }}; mock.complexOperation(null); } @Test public void expectInvocationWithNotNullArgument() { new StrictExpectations() {{ mock.complexOperation(withNotNull()); }}; mock.complexOperation(true); } @Test public void expectInvocationWithSameInstance() { new StrictExpectations() {{ mock.complexOperation(withSameInstance(45L)); }}; mock.complexOperation(45L); } @Test public void expectInvocationWithSubstring() { new StrictExpectations() {{ mock.complexOperation(withSubstring("sub")); }}; mock.complexOperation("abcsub\r\n123"); } @Test public void expectInvocationWithPrefix() { new StrictExpectations() {{ mock.complexOperation(withPrefix("abc")); }}; mock.complexOperation("abc\tsub\"123\""); } @Test public void expectInvocationWithSuffix() { new StrictExpectations() {{ mock.complexOperation(withSuffix("123")); }}; mock.complexOperation("abcsub123"); } @Test public void expectInvocationWithMatchForRegex() { new StrictExpectations() {{ mock.complexOperation(withMatch("[a-z]+[0-9]*")); mock.complexOperation(withMatch("(?i)[a-z]+sub[0-9]*")); }}; mock.complexOperation("abcsub123"); mock.complexOperation("abcSuB123"); } @Test public void expectInvocationWithUserProvidedMatcher() { new StrictExpectations() {{ mock.setValue(withArgThat(is(equalTo(3)))); }}; mock.setValue(3); } @Test public void expectInvocationWithUserImplementedMatcherUsingHamcrestAPI() { new StrictExpectations() {{ mock.complexOperation(withArgThat(new BaseMatcher<Integer>() { @Override public boolean matches(Object item) { Integer value = (Integer) item; return value >= 10 && value <= 100; } @Override public void describeTo(Description description) { description.appendText("between 10 and 100"); } })); }}; mock.complexOperation(28); } @Test public void expectInvocationsWithAnonymousDelegateMatchers() { new StrictExpectations() {{ mock.setValue(with(new Delegate<Integer>() { @Mock boolean matches(int value) { return value >= 10 && value <= 100; } })); mock.setValue(with(new Delegate<Double>() { @Mock void validate(double value) { assertTrue("value outside of 20-80 range", value >= 20.0 && value <= 80.0); } })); }}; new StrictExpectations() {{ mock.setValue(with(new Delegate<String>() { @Mock boolean validLength(String value) { return value.length() >= 10 && value.length() <= 100; } })); mock.setValue(with(new Delegate<Float>() { @Mock boolean positive(float value) { return value > 0.0F; } })); }}; new StrictExpectations() {{ mock.setTextualValues(with(new Delegate<Collection<String>>() { @Mock boolean validSize(Collection<String> values) { return values.size() >= 1; } })); mock.setTextualValues(with(new Delegate<Collection<String>>() { @Mock boolean randomAccess(Iterable<String> values) { return values instanceof RandomAccess; } })); }}; mock.setValue(28); mock.setValue(20.0); mock.setValue("Test 123 abc"); mock.setValue(1.5F); List<String> values1 = asList("a", "B", "c"); mock.setTextualValues(values1); Collection<String> values2 = asList("1", "2", "3"); mock.setTextualValues(values2); } static final class CollectionElementDelegate<T> implements Delegate<Collection<T>> { private final T item; CollectionElementDelegate(T item) { this.item = item; } @SuppressWarnings("unused") boolean hasItem(Collection<T> items) { return items.contains(item); } } static final class Delegates { static <T> Delegate<Collection<T>> collectionElement(T item) { return new CollectionElementDelegate<T>(item); } } @Test public void expectInvocationsWithNamedDelegateMatcher() { new StrictExpectations() {{ mock.setTextualValues(with(collectionElement("B"))); }}; List<String> values = asList("a", "B", "c"); mock.setTextualValues(values); } @Test public void expectInvocationsWithHamcrestMatcher() { new StrictExpectations() {{ mock.setTextualValues(this.<Collection<String>>withArgThat(hasItem("B"))); }}; List<String> values = asList("a", "B", "c"); mock.setTextualValues(values); } @Test public void expectInvocationWithMatcherContainingAnotherMatcher() { new StrictExpectations() {{ mock.setValue(withArgThat(equalTo(3))); }}; mock.setValue(3); } @Test public void useMockedMethodBeforeRecordingExpectationWithArgumentMatcher() { assertFalse(mock.doSomething("abc")); new Expectations() {{ mock.doSomething(anyString); result = true; }}; assertTrue(mock.doSomething("xyz")); assertTrue(mock.doSomething("abc")); } @Test public void recordExpectationsUsingTheAnyFieldsForParameterOfTypeObject() { new Expectations() {{ mock.useObject(anyString); result = "String"; mock.useObject(anyInt); result = "int"; mock.useObject(anyByte); result = "byte"; mock.useObject(anyShort); result = "short"; mock.useObject(anyLong); result = "long"; mock.useObject(anyBoolean); result = "boolean"; mock.useObject(anyChar); result = "char"; mock.useObject(anyFloat); result = "float"; mock.useObject(anyDouble); result = "double"; mock.useObject(any); result = "Object"; }}; assertInvocationsWithArgumentsOfDifferentTypesToMethodAcceptingAnyObject(); } void assertInvocationsWithArgumentsOfDifferentTypesToMethodAcceptingAnyObject() { assertEquals("String", mock.useObject("test")); assertEquals("String", mock.useObject(null)); // uses the first recorded expectation, since they all match null assertEquals("int", mock.useObject(2)); assertEquals("Object", mock.useObject(new Object())); assertEquals("byte", mock.useObject((byte) -3)); assertEquals("short", mock.useObject((short) 4)); assertEquals("long", mock.useObject(-5L)); assertEquals("boolean", mock.useObject(true)); assertEquals("boolean", mock.useObject(false)); assertEquals("char", mock.useObject('A')); assertEquals("float", mock.useObject(-1.5F)); assertEquals("double", mock.useObject(23.456)); } @Test public void recordExpectationsUsingTheWithAnyMethodForParameterOfTypeObject() { new Expectations() {{ mock.useObject(withAny("a")); result = "String"; mock.useObject(withAny(2)); result = "int"; mock.useObject(withAny((byte) 3)); result = "byte"; mock.useObject(withAny((short) 4)); result = "short"; mock.useObject(withAny(5L)); result = "long"; mock.useObject(withAny(true)); result = "boolean"; mock.useObject(withAny('\0')); result = "char"; mock.useObject(withAny(0.41F)); result = "float"; mock.useObject(withAny(0.41)); result = "double"; mock.useObject(withAny(new Object())); result = "Object"; }}; assertInvocationsWithArgumentsOfDifferentTypesToMethodAcceptingAnyObject(); } @Test public void declareFieldInExpectationBlockWithNameHavingSamePrefixAsArgumentMatchingField() { new Expectations() { final Integer anyValue = 1; { mock.setValue(anyValue); } }; mock.setValue(1); } @Test public void declareMethodInExpectationBlockWithNameHavingSamePrefixAsArgumentMatchingMethod() { final List<Integer> values = new ArrayList<Integer>(); new Expectations() { { mock.setValues(withEqual('c'), anyBoolean); mock.setValue(withCapture(values)); } char withEqual(char c) { return c; } }; mock.setValues('c', true); final Collaborator col = new Collaborator(); col.setValue(1); assertEquals(singletonList(1), values); new Verifications() {{ int i; mock.setValue(i = withCapture()); assertEquals(1, i); List<Collaborator> collaborators = withCapture(new Collaborator()); assertSame(col, collaborators.get(0)); }}; } // "Missing invocations" for regular/strict expectations /////////////////////////////////////////////////////////// @Test public void expectInvocationWithSameMockInstanceButReplayWithNull( // This class defines an abstract "toString" override, which initially was erroneously // mocked, causing a new expectation to be created during replay: @Mocked final Certificate cert) { new Expectations() {{ mock.setValue(withSameInstance(cert)); times = 1; }}; mock.setValue((Certificate) null); thrown.expect(MissingInvocation.class); } @Test public void expectNotStrictInvocationWithMatcherWhichInvokesMockedMethod() { new Expectations() {{ mock.setValue(with(new Delegate<Integer>() { @Mock boolean validateAsPositive(int value) { // Invoking mocked method caused ConcurrentModificationException (bug fixed): mock.simpleOperation(1, "b", null); return value > 0; } })); }}; mock.setValue(-3); thrown.expect(MissingInvocation.class); } @Test public void expectStrictInvocationWithCustomMatcherButNeverReplay() { new StrictExpectations() {{ mock.doSomething(with(new Delegate<Integer>() { @Mock boolean test(Integer i) { return true; } })); }}; thrown.expect(MissingInvocation.class); } // Verifications /////////////////////////////////////////////////////////////////////////////////////////////////// class ReusableMatcher implements Delegate<Integer> { @Mock final boolean isPositive(int i) { return i > 0; } } @Test public void extendingAReusableArgumentMatcher() { mock.setValue(5); mock.setValue(123); new Verifications() {{ mock.setValue(with(new ReusableMatcher() {})); times = 2; }}; } }