<version>${project.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>testutils</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</dependencyManagement>
<module>object-cache-guava</module>
<module>object-cache-noop</module>
<module>util</module>
+ <module>testutils</module>
</modules>
<!--
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>bundle-parent</artifactId>
+ <version>1.8.0-SNAPSHOT</version>
+ <relativePath/>
+ </parent>
+
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>testutils</artifactId>
+ <version>1.1.0-SNAPSHOT</version>
+ <!-- Currently not needed, maybe later: <packaging>bundle</packaging> -->
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yangtools-artifacts</artifactId>
+ <version>${project.version}</version>
+ <scope>import</scope>
+ <type>pom</type>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <!-- NOTE: The use of <scope> here is a little particular, compared to other standard projects...
+
+ As this test helper project is intended to itself be used as a <scope>test
+ <dependency> so that the utility code in src/main/java of this project can
+ be used to write src/test/java code in projects using it, all <dependencies>
+ here are <scope>compile here (the default, don't mention it), and NOT <scope>test.
+ (Only a <dependency> which only this project would want to use in its own src/test/java code
+ but not expose to projects depending on it would be <scope>test. However that kind of against
+ the whole point of this project, and currently there no such dependencies here.
+ -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.truth</groupId>
+ <artifactId>truth</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.xtend</groupId>
+ <artifactId>org.eclipse.xtend.lib</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.eclipse.xtend</groupId>
+ <artifactId>xtend-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2016 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.testutils.mockito;
+
+import com.google.common.annotations.Beta;
+import java.io.Serializable;
+import java.lang.reflect.Modifier;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Mockito Answer which for un-stubbed methods forwards the call to the real
+ * method if it is implemented on the mocked object (i.e. not an interface or
+ * abstract method), and otherwise throws an {@link UnstubbedMethodException}, like the
+ * {@link ThrowsMethodExceptionAnswer}.
+ *
+ * <p>
+ * This can be useful to create light-weight <a href=
+ * "http://googletesting.blogspot.ch/2013/07/testing-on-toilet-know-your-test-doubles.html">Fake Doubles</a>
+ * (in particular some with state). For example:
+ *
+ * <pre>
+ * import static ...testutils.mockito.MoreAnswers.realOrException;
+ *
+ * interface Service {
+ * List<Thing> getThings();
+ * boolean installThing(Thing thing);
+ * }
+ *
+ * abstract class FakeService implements Service {
+ * // Ignore getThings() - we don't need that for this test
+ * boolean installThing(Thing thing) {
+ * LOGGER.log("not really installed");
+ * return false;
+ * }
+ * }
+ *
+ * Service fake = Mockito.mock(FakeService.class, realOrException())
+ * </pre>
+ *
+ * <p>
+ * TIP: An impact of Mockito is that, just like in standard Mockito, constructors
+ * (and thus field initializers) are not called. So in your abstract fake class,
+ * instead of:
+ *
+ * <pre>
+ * abstract class FakeService implements Service {
+ * private final List<Thing> things = new ArrayList<>();
+ *
+ * public List<Thing> getThings() {
+ * return things;
+ * }
+ *
+ * @Override
+ * public boolean installThing(Thing thing) {
+ * return things.add(thing);
+ * }
+ * }
+ * </pre>
+ *
+ * <p>
+ * you'll just need to do:
+ *
+ * <pre>
+ * abstract class FakeService implements Service {
+ * private List<Thing> things;
+ *
+ * public List<Thing> getThings() {
+ * if (things == null)
+ * things = new ArrayList<>()
+ * return things;
+ * }
+ *
+ * @Override
+ * public boolean installThing(Thing thing) {
+ * return getThings().add(thing);
+ * }
+ * }
+ * </pre>
+ *
+ * <p>
+ * The big advantage of Mikitos versus just writing classes implementing service
+ * interfaces without using Mockito at all is that you don't have to implement a
+ * lot of methods you don't care about - you can just make an abstract fake
+ * class (incl. e.g. an inner class in your Test) and implement only one or some
+ * methods. This keeps code shorter and thus more readable.
+ *
+ * <p>
+ * The advantage of Mikitos VS pure Mockito's when/thenAnswer are that they:
+ * <ul>
+ *
+ * <li>are fully type safe and refactoring resistant; whereas Mockito is not,
+ * e.g. for return values with doReturn(...).when(), and uses runtime instead of
+ * compile time error reporting for this.</li>
+ * <li>avoid confusion re. the alternative doReturn(...).when() syntax required
+ * with ThrowsMethodExceptionAnswer instead of when(...).thenReturn()</li>
+ * <li>enforce the ThrowsMethodExceptionAnswer by default for
+ * non-implemented methods (which is possible with Mockito by explicitly passing
+ * this, but is easily forgotten)</li>
+ * </ul>
+ *
+ * @see Mockito#mock(Class, Answer)
+ * @see ThrowsMethodExceptionAnswer
+ * @see Mockito#CALLS_REAL_METHODS
+ * @see Mockito#CALLS_REAL_METHODS
+ *
+ * @author Michael Vorburger
+ */
+@Beta
+public class CallsRealOrExceptionAnswer implements Answer<Object>, Serializable {
+ private static final long serialVersionUID = -3730024662402964588L;
+
+ /**
+ * Use {@link MoreAnswers} to obtain an instance.
+ */
+ CallsRealOrExceptionAnswer() {
+ }
+
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ if (Modifier.isAbstract(invocation.getMethod().getModifiers())) {
+ throw new UnstubbedMethodException(invocation.getMethod(), invocation.getMock());
+ }
+ return invocation.callRealMethod();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.testutils.mockito;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.lang.reflect.Type;
+
+/**
+ * Nicer shorter toString() for {@link Method} than it's default.
+ *
+ * <p>Without modifiers, return type, FQN of class and exceptions; instead with parameter names (if javac -parameters).
+ *
+ * @author Michael Vorburger
+ */
+public final class MethodExtensions {
+
+ private MethodExtensions() {
+ }
+
+ public static String toString(Method method) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(method.getName());
+
+ // copy/paste from java.lang.reflect.Executable.sharedToGenericString(int, boolean)
+ sb.append('(');
+ Type[] params = method.getGenericParameterTypes();
+ Parameter[] parameters = method.getParameters(); // NEW
+ for (int j = 0; j < params.length; j++) {
+ String param = params[j].getTypeName();
+ if (method.isVarArgs() && j == params.length - 1) { // replace T[] with T...
+ param = param.replaceFirst("\\[\\]$", "...");
+ }
+ sb.append(param);
+ // NEW
+ if (parameters[j].isNamePresent()) {
+ sb.append(' ');
+ sb.append(parameters[j].getName());
+ }
+ // NEW END
+ if (j < (params.length - 1)) {
+ sb.append(", "); // NEW ", " instead of ','
+ }
+ }
+ sb.append(')');
+
+ return sb.toString();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.testutils.mockito;
+
+import org.mockito.AdditionalAnswers;
+import org.mockito.Answers;
+import org.mockito.stubbing.Answer;
+
+/**
+ * More {@link Answer} variants.
+ *
+ * @see Answers
+ * @see AdditionalAnswers
+ *
+ * @author Michael Vorburger
+ */
+@SuppressWarnings("unchecked")
+public final class MoreAnswers {
+
+ private static final CallsRealOrExceptionAnswer REAL_OR_EXCEPTION
+ = new CallsRealOrExceptionAnswer();
+
+ private static final ThrowsMethodExceptionAnswer EXCEPTION
+ = new ThrowsMethodExceptionAnswer();
+
+ private MoreAnswers() {
+ }
+
+ /**
+ * Returns Mockito Answer (default) which forwards method calls or throws an UnstubbedMethodException.
+ *
+ * @see CallsRealOrExceptionAnswer
+ */
+ public static <T> Answer<T> realOrException() {
+ return (Answer<T>) REAL_OR_EXCEPTION;
+ }
+
+ /**
+ * Returns Mockito Answer (default) which throws an UnstubbedMethodException.
+ *
+ * @see ThrowsMethodExceptionAnswer
+ */
+ public static <T> Answer<T> exception() {
+ return (Answer<T>) EXCEPTION;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.testutils.mockito;
+
+import com.google.common.annotations.Beta;
+import java.io.Serializable;
+import org.mockito.Mockito;
+import org.mockito.internal.stubbing.answers.ThrowsException;
+import org.mockito.internal.stubbing.answers.ThrowsExceptionClass;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Mockito Answer which for un-stubbed methods throws an
+ * UnstubbedMethodException (instead of Mockito's default of returning null).
+ *
+ * <p>
+ * Usage:
+ *
+ * <pre>
+ * import static ...testutils.mockito.MoreAnswers.exception;
+ *
+ * Mockito.mock(YourInterface.class, exception())
+ * </pre>
+ *
+ * @see Mockito#mock(Class, Answer)
+ *
+ * @see ThrowsException
+ * @see ThrowsExceptionClass
+ *
+ * @author Michael Vorburger
+ */
+@Beta
+public class ThrowsMethodExceptionAnswer implements Answer<Object>, Serializable {
+ private static final long serialVersionUID = -7316574192253912318L;
+
+ /**
+ * Use {@link MoreAnswers} to obtain an instance.
+ */
+ ThrowsMethodExceptionAnswer() {
+ }
+
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ throw new UnstubbedMethodException(invocation.getMethod());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Red Hat and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.testutils.mockito;
+
+import java.lang.reflect.Method;
+import org.mockito.internal.util.MockUtil;
+
+/**
+ * Exception to be thrown on unstubbed method calls.
+ *
+ * @author Michael Vorburger
+ */
+public class UnstubbedMethodException extends UnsupportedOperationException {
+ private static final long serialVersionUID = 1L;
+
+ public UnstubbedMethodException(Method method) {
+ super(MethodExtensions.toString(method) + " is not stubbed in mock of " + method.getDeclaringClass().getName());
+ }
+
+ public UnstubbedMethodException(Method method, Object mockAbstractFakeObject) {
+ super(MethodExtensions.toString(method) + " is not implemented in "
+ + new MockUtil().getMockName(mockAbstractFakeObject).toString());
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.testutils.mockito.tests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.lang.reflect.Method;
+import org.junit.Test;
+import org.opendaylight.yangtools.testutils.mockito.MethodExtensions;
+
+public class MethodExtensionsTest {
+
+ public <T> void fooBar(int index, T element) {
+ }
+
+ @Test
+ public void betterToString() throws Exception {
+ Method method = MethodExtensionsTest.class.getMethod("fooBar", Integer.TYPE, Object.class);
+ assertThat(MethodExtensions.toString(method)).isEqualTo("fooBar(int index, T element)");
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.testutils.mockito.tests;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.opendaylight.yangtools.testutils.mockito.MoreAnswers.realOrException;
+
+import java.io.File;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.opendaylight.yangtools.testutils.mockito.UnstubbedMethodException;
+
+/**
+ * Test to illustrate the use of the REAL_OR_EXCEPTION.
+ *
+ * <p>Also useful as example to contrast this approach illustrated in the MockitoExampleTutorialTest.
+ *
+ * @see MockitoExampleTutorialTest
+ *
+ * @author Michael Vorburger
+ */
+public class MikitoTest {
+
+ interface SomeService {
+
+ void foo();
+
+ String bar(String arg);
+
+ // Most methods on real world services have complex input (and output objects), not just int or String
+ int foobar(File file);
+ }
+
+ @Test
+ public void usingMikitoToCallStubbedMethod() {
+ SomeService service = Mockito.mock(MockSomeService.class, realOrException());
+ assertEquals(123, service.foobar(new File("hello.txt")));
+ assertEquals(0, service.foobar(new File("belo.txt")));
+ }
+
+ @Test
+ public void usingMikitoToCallUnstubbedMethodAndExpectException() {
+ MockSomeService service = Mockito.mock(MockSomeService.class, realOrException());
+ try {
+ service.foo();
+ fail();
+ } catch (UnstubbedMethodException e) {
+ assertThat(e.getMessage()).isEqualTo("foo() is not implemented in mockSomeService");
+ }
+ }
+
+ abstract static class MockSomeService implements SomeService {
+ @Override
+ public int foobar(File file) {
+ if (file.getName().equals("hello.txt")) {
+ return 123;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.testutils.mockito.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.opendaylight.yangtools.testutils.mockito.MoreAnswers.exception;
+
+import java.io.File;
+import org.junit.Test;
+import org.opendaylight.yangtools.testutils.mockito.UnstubbedMethodException;
+
+/**
+ * Test to illustrate the basic use of Mockito VS the EXCEPTION_ANSWER.
+ *
+ * <p>Also useful as example to contrast this approach with the REAL_OR_EXCEPTION
+ * approach illustrated in the MikitoTest.
+ *
+ * @see MikitoTest
+ *
+ * @author Michael Vorburger
+ */
+public class MockitoExampleTutorialTest {
+
+ interface SomeService {
+
+ void foo();
+
+ String bar(String arg);
+
+ // Most methods on real world services have complex input (and output objects), not just int or String
+ int foobar(File file);
+ }
+
+ @Test
+ public void usingMockitoWithoutStubbing() {
+ SomeService service = mock(SomeService.class);
+ assertNull(service.bar("hulo"));
+ }
+
+ @Test
+ public void usingMockitoToStubSimpleCase() {
+ SomeService service = mock(SomeService.class);
+ when(service.foobar(any())).thenReturn(123);
+ assertEquals(123, service.foobar(new File("hello.txt")));
+ }
+
+ @Test
+ public void usingMockitoToStubComplexCase() {
+ SomeService service = mock(SomeService.class);
+ when(service.foobar(any())).thenAnswer(invocation -> {
+ // Urgh! This is ugly.. (Mockito 2.0 may be better, see http://site.mockito.org/mockito/docs/current/org/mockito/ArgumentMatcher.html)
+ File file = (File) invocation.getArgumentAt(0, File.class);
+ if (file.getName().equals("hello.txt")) {
+ return 123;
+ } else {
+ return 0;
+ }
+ });
+ assertEquals(0, service.foobar(new File("belo.txt")));
+ }
+
+ @Test(expected = UnstubbedMethodException.class)
+ public void usingMockitoExceptionException() {
+ SomeService service = mock(SomeService.class, exception());
+ service.foo();
+ }
+
+ @Test
+ public void usingMockitoNoExceptionIfStubbed() {
+ SomeService service = mock(SomeService.class, exception());
+ // NOT when(s.foobar(any())).thenReturn(123) BUT must be like this:
+ doReturn(123).when(service).foobar(any());
+ assertEquals(123, service.foobar(new File("hello.txt")));
+ try {
+ service.foo();
+ fail("expected NotImplementedException");
+ } catch (UnstubbedMethodException e) {
+ // OK
+ }
+ }
+
+ @Test
+ public void usingMockitoToStubComplexCaseAndExceptionIfNotStubbed() {
+ SomeService service = mock(SomeService.class, exception());
+ doAnswer(invocation -> {
+ // Urgh! This is ugly. Mockito may be better, see http://site.mockito.org/mockito/docs/current/org/mockito/ArgumentMatcher.html
+ File file = (File) invocation.getArguments()[0];
+ if (file.getName().equals("hello.txt")) {
+ return 123;
+ } else {
+ return 0;
+ }
+ }).when(service).foobar(any());
+ assertEquals(123, service.foobar(new File("hello.txt")));
+ assertEquals(0, service.foobar(new File("belo.txt")));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.testutils.mockito.tests;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+import static org.opendaylight.yangtools.testutils.mockito.MoreAnswers.exception;
+
+import java.io.Closeable;
+import java.io.IOException;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.opendaylight.yangtools.testutils.mockito.UnstubbedMethodException;
+
+public class MockitoUnstubbedMethodExceptionAnswerTest {
+
+ @Test
+ public void testAnswering() throws IOException {
+ Closeable mock = Mockito.mock(Closeable.class, exception());
+ try {
+ mock.close();
+ fail();
+ } catch (UnstubbedMethodException e) {
+ assertThat(e.getMessage()).isEqualTo("close() is not stubbed in mock of java.io.Closeable");
+ }
+
+ }
+
+}
<dependency>
<groupId>org.opendaylight.yangtools</groupId>
<artifactId>yangtools-artifacts</artifactId>
- <version>1.1.0-SNAPSHOT</version>
+ <version>${project.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>