com.roscopeco.moxy.Moxy Maven / Gradle / Ivy
/*
* Moxy - Lean-and-mean mocking framework for Java with a fluent API.
*
* Copyright 2018 Ross Bamford
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.roscopeco.moxy;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import com.roscopeco.moxy.api.InvocationRunnable;
import com.roscopeco.moxy.api.InvocationSupplier;
import com.roscopeco.moxy.api.MoxyClassMockEngine;
import com.roscopeco.moxy.api.MoxyEngine;
import com.roscopeco.moxy.api.MoxyException;
import com.roscopeco.moxy.api.MoxyMultiVerifier;
import com.roscopeco.moxy.api.MoxyStubber;
import com.roscopeco.moxy.api.MoxyVerifier;
import com.roscopeco.moxy.api.MoxyVoidStubber;
import com.roscopeco.moxy.matchers.Matchers;
/**
* Moxy is a type-safe mocking/spying framework for Java with a fluent API.
*
* It takes inspiration from a number of other popular mocking frameworks,
* notably Mockito. It aims to be lean, fast, and as far as possible to lack
* the "surprises" one finds in other mocking frameworks (e.g. it should not
* give weird failures in unrelated tests, but instead should fail fast when
* used improperly).
*
* This class, along with the {@link Matchers} class, are the main top-level
* classes most users will need to interact with when using Moxy. They are
* designed so you can simply import static
and start mocking.
* For example:
*
*
* import static com.roscopeco.moxy.Moxy.*;
* import static com.roscopeco.moxy.matchers.Matchers.*;
*
* // ... later ...
*
* SomeClass mock = mock(SomeClass.class);
*
* when(() -> mock.someMethod(any(), eq("something"))).thenReturn("whatever");
*
* // ... mock usage ...
*
* assertMock(() -> mock.someMethod("expected", "something").wasCalledTwice();
* assertMock(() -> mock.someMethod("badfood", "something").wasNotCalled();
*
*
* See README.md
for more detailed usage of the library.
*
* @author Ross Bamford <roscopeco AT gmail DOT com>
* @since 1.0
* @see com.roscopeco.moxy.matchers.Matchers
*/
public final class Moxy {
private static final String PRIMITIVE_BYTE_TYPE = "byte";
private static final String PRIMITIVE_CHAR_TYPE = "char";
private static final String PRIMITIVE_SHORT_TYPE = "short";
private static final String PRIMITIVE_INT_TYPE = "int";
private static final String PRIMITIVE_LONG_TYPE = "long";
private static final String PRIMITIVE_FLOAT_TYPE = "float";
private static final String PRIMITIVE_DOUBLE_TYPE = "double";
private static final String PRIMITIVE_BOOLEAN_TYPE = "boolean";
private static final Class>[] MOXY_ENGINE_SINGLETON_ARRAY =
new Class>[] { MoxyEngine.class };
private static final Logger LOG = Logger.getLogger(Moxy.class.getName());
private static MoxyEngine moxyEngine;
private static MoxyClassMockEngine moxyClassMockEngine;
private Moxy() {
throw new UnsupportedOperationException(
"com.roscopeco.moxy.Moxy is not designed for instantiation");
}
private static MoxyEngine instantiateDefaultMoxyEngine() {
final String defaultEngineName = System.getProperty(
"com.roscopeco.moxy.engine.standard", "com.roscopeco.moxy.impl.asm.ASMMoxyEngine");
try {
final Class> defaultEngine = Class.forName(defaultEngineName);
if (MoxyEngine.class.isAssignableFrom(defaultEngine)) {
return (MoxyEngine)defaultEngine.getDeclaredConstructor().newInstance();
} else {
throw new MoxyException("Invalid configuration: '" + defaultEngineName + " is not a MoxyEngine implementation");
}
} catch (InvocationTargetException |
ClassNotFoundException |
InstantiationException |
IllegalAccessException |
NoSuchMethodException e) {
throw new MoxyException("Invalid configuration: Unable to instantiate MoxyEngine; See cause", e);
}
}
private static MoxyClassMockEngine instantiateDefaultMoxyClassMockEngine() {
final String defaultEngineName = System.getProperty(
"com.roscopeco.moxy.engine.classmock", "com.roscopeco.moxy.impl.asm.classmock.ASMClassMockEngine");
try {
final Class> defaultEngine = Class.forName(defaultEngineName);
if (MoxyClassMockEngine.class.isAssignableFrom(defaultEngine)) {
return (MoxyClassMockEngine)defaultEngine.getDeclaredConstructor().newInstance();
} else {
throw new MoxyException("Invalid configuration: '" + defaultEngineName + " is not a MoxyClassMockEngine implementation");
}
} catch (InvocationTargetException |
ClassNotFoundException |
InstantiationException |
IllegalAccessException |
NoSuchMethodException e) {
throw new MoxyException("Invalid configuration: Unable to instantiate MoxyClassMockEngine; See cause", e);
}
}
private static MoxyEngine ensureMoxyEngine() {
if (moxyEngine == null) {
moxyEngine = instantiateDefaultMoxyEngine();
}
return moxyEngine;
}
private static MoxyClassMockEngine ensureMoxyClassMockEngine() {
if (moxyClassMockEngine == null) {
moxyClassMockEngine = instantiateDefaultMoxyClassMockEngine();
}
return moxyClassMockEngine;
}
/**
* Set the {@link MoxyEngine} that will be used by all future calls
* to methods in this class.
*
* Note: Calling this method after the current
* (or default) engine has been used may cause problems. If you are
* changing the engine, you should change it before interacting
* with the library in any other way (especially creating mocks).
*
* @param moxyEngine The {@link MoxyEngine} implementation to use.
*
* @since 1.0
*/
public static void setMoxyEngine(final MoxyEngine moxyEngine) {
if (Moxy.moxyEngine != null) {
LOG.warning(() -> "Changing an in-use Moxy engine may cause unwanted side effects");
}
Moxy.moxyEngine = moxyEngine;
}
/**
* Get the current {@link MoxyEngine} in use by this class, or
* create and return a default engine if none has been set.
*
* @return The {@link MoxyEngine} currently in use.
*
* @since 1.0
*/
public static MoxyEngine getMoxyEngine() {
return Moxy.ensureMoxyEngine();
}
/**
* Set the {@link MoxyClassMockEngine} that will be used by all
* future calls to methods in this class.
*
* Note: Calling this method after the current
* (or default) engine has been used may cause problems. If you are
* changing the engine, you should change it before interacting
* with the library in any other way (especially creating mocks).
*
* @param moxyClassMockEngine The {@link MoxyClassMockEngine} implementation to use.
*
* @since 1.0
*/
public static void setMoxyClassMockEngine(final MoxyClassMockEngine moxyClassMockEngine) {
if (Moxy.moxyClassMockEngine != null) {
LOG.warning(() -> "Changing an in-use class mock engine may cause unwanted side effects");
}
Moxy.moxyClassMockEngine = moxyClassMockEngine;
}
/**
* Get the current {@link MoxyClassMockEngine} in use by this class, or
* create and return a default engine if none has been set.
*
* @return The {@link MoxyClassMockEngine} currently in use.
*
* @since 1.0
*/
public static MoxyClassMockEngine getMoxyClassMockEngine() {
return Moxy.ensureMoxyClassMockEngine();
}
/**
* Convert the given classes to mock classes using the
* default {@link MoxyClassMockEngine}.
*
* when this method returns, all instances of the given classes will
* be converted to mocks, along with all future instances.
*
* This is not the normal style of mocking.
* See {@link MoxyClassMockEngine} for details.
*
* @param classes Classes to convert.
*
* @see MoxyClassMockEngine
* @see MoxyClassMockEngine#mockClasses(Class...)
* @since 1.0
*/
public static void mockClasses(final Class>... classes) {
mockClasses(ensureMoxyClassMockEngine(), classes);
}
/**
* Convert the given classes to mock classes using the
* supplied {@link MoxyClassMockEngine}.
*
* when this method returns, all instances of the given classes will
* be converted to mocks, along with all future instances.
*
* This is not the normal style of mocking.
* See {@link MoxyClassMockEngine} for details.
*
* @param engine The {@link MoxyClassMockEngine} to use.
* @param classes Classes to convert.
*
* @see MoxyClassMockEngine
* @see MoxyClassMockEngine#mockClasses(Class...)
* @since 1.0
*/
public static void mockClasses(final MoxyClassMockEngine engine, final Class>... classes) {
engine.mockClasses(classes);
}
/**
* Reset the given classes to their original, non-mock implementation,
* using the default {@link MoxyClassMockEngine}.
*
* @param classes Classes to reset.
*
* @see #mockClasses(Class...)
* @see MoxyClassMockEngine
* @see MoxyClassMockEngine#mockClasses(Class...)
* @since 1.0
*/
public static void resetClassMocks(final Class>... classes) {
resetClassMocks(ensureMoxyClassMockEngine(), classes);
}
/**
* Reset the given classes to their original, non-mock implementation,
* using the supplied {@link MoxyClassMockEngine}.
*
* @param engine The {@link MoxyClassMockEngine} to use.
* @param classes Classes to reset.
*
* @see #mockClasses(MoxyClassMockEngine, Class...)
* @see MoxyClassMockEngine
* @see MoxyClassMockEngine#mockClasses(Class...)
* @since 1.0
*/
public static void resetClassMocks(final MoxyClassMockEngine engine, final Class>... classes) {
engine.resetClasses(classes);
}
/**
* Reset all class mocks created with the default {@link MoxyClassMockEngine}
* to their original, un-mocked state.
*
* @see #resetClassMocks(Class...)
* @since 1.0
*/
public static void resetAllClassMocks() {
resetAllClassMocks(ensureMoxyClassMockEngine());
}
/**
* Reset all class mocks created with the supplied {@link MoxyClassMockEngine}
* to their original, un-mocked state.
*
* @param engine The {@link MoxyClassMockEngine} to use.
*
* @see #resetClassMocks(MoxyClassMockEngine, Class...)
* @since 1.0
*/
public static void resetAllClassMocks(final MoxyClassMockEngine engine) {
engine.resetAllClasses();
}
/**
* Create a mock instance of the given class using the
* currently-set (or default) {@link MoxyEngine}.
*
* The mock will be created in the engine's default
* ClassLoader
. For the default engine, this
* is the same ClassLoader
that loaded the
* Moxy framework.
*
* Note: This method will not call a constructor
* on the generated instance, which may cause problems if you plan
* to use #{@link MoxyStubber#thenCallRealMethod()} with this mock.
*
* If that is the case, see
* {@link #constructSpy(Class, Object...)}.
*
* @param clz The Class
to mock.
*
* @return A new mock instance.
*
* @param The type being mocked.
* @see #mock(Class, PrintStream)
* @see #mock(MoxyEngine, Class, PrintStream)
* @since 1.0
*/
public static T mock(final Class clz) {
return mock(clz, (PrintStream)null);
}
/**
* Create a mock instance of the given class using the
* currently-set (or default) {@link MoxyEngine}.
*
* The mock will be created in the engine's default
* ClassLoader
. For the default engine, this
* is the same ClassLoader
that loaded the
* Moxy framework.
*
* During creation of the mock, this method will also dump the
* generated bytecode, in a debugging format, to the supplied
* PrintStream
.
*
* Note: This method will not call a constructor
* on the generated instance, which may cause problems if you plan
* to use #{@link MoxyStubber#thenCallRealMethod()} with this mock.
*
* If that is the case, see
* {@link #constructSpy(Class, PrintStream, Object...)}.
*
* @param clz The Class
to mock.
* @param trace If non-null, the resulting class will be dumped (with a TraceClassVisitor
) to the given stream.
*
* @return A new mock instance.
*
* @param The type being mocked.
* @see #mock(Class)
* @see #mock(MoxyEngine, Class, PrintStream)
* @since 1.0
*/
public static T mock(final Class clz, final PrintStream trace) {
return mock(ensureMoxyEngine(), clz, trace);
}
/**
* Create a mock instance of the given class using the
* specified {@link MoxyEngine}.
*
* The mock will be created in the engine's default
* ClassLoader
. For the default engine, this
* is the same ClassLoader
that loaded the
* Moxy framework.
*
* During creation of the mock, this method will also dump the
* generated bytecode, in a debugging format, to the supplied
* PrintStream
.
*
* Note: This method will not call a constructor
* on the generated instance, which may cause problems if you plan
* to use #{@link MoxyStubber#thenCallRealMethod()} with this mock.
*
* If that is the case, see
* {@link #constructSpy(MoxyEngine, Class, PrintStream, Object...)}.
*
* @param engine The {@link MoxyEngine} implementation to use.
* @param clz The Class
to mock.
* @param trace If non-null, the resulting class will be dumped (with a TraceClassVisitor
) to the given stream.
*
* @return A new mock instance.
*
* @param The type being mocked.
* @see #mock(Class)
* @see #mock(Class, PrintStream)
* @since 1.0
*/
public static T mock(final MoxyEngine engine, final Class clz, final PrintStream trace) {
if (clz == null) {
throw new IllegalArgumentException("Cannot mock null");
}
if (engine == null) {
throw new IllegalArgumentException("Cannot mock with null engine");
}
return engine.mock(clz, trace);
}
/**
* Create a mock instance of the given class using the
* default {@link MoxyEngine} using a constructor call.
* This method is intended for use when one or more methods
* will be spied (with {@link MoxyStubber#thenCallRealMethod()}.
*
* The mock will be created in the engine's default
* ClassLoader
. For the default engine, this
* is the same ClassLoader
that loaded the
* Moxy framework.
*
* This method uses a best-fit algorithm to determine
* which constructor to call based on the supplied arguments.
* Primitive types will be boxed/unboxed automatically.
*
* @param clz The Class
to spy.
* @param args Constructor arguments for the mock.
*
* @return A new mock instance.
*
* @param The type being mocked.
* @see #mock(Class)
* @see #constructMock(Class, PrintStream, Object...)
* @see #constructMock(MoxyEngine, Class, PrintStream, Object...)
* @see MoxyStubber#thenCallRealMethod()
* @since 1.0
*/
@SafeVarargs
public static T constructMock(final Class clz, final Object... args) {
return constructMock(clz, null, args);
}
/**
* Create a mock instance of the given class using the
* default {@link MoxyEngine} using a constructor call.
* This method is intended for use when one or more methods
* will be spied (with {@link MoxyStubber#thenCallRealMethod()}.
*
* The mock will be created in the engine's default
* ClassLoader
. For the default engine, this
* is the same ClassLoader
that loaded the
* Moxy framework.
*
* During creation of the mock, this method will also dump the
* generated bytecode, in a debugging format, to the supplied
* PrintStream
(if non-null
).
*
* This method uses a best-fit algorithm to determine
* which constructor to call based on the supplied arguments.
* Primitive types will be boxed/unboxed automatically.
*
* @param clz The Class
to spy.
* @param trace If non-null, the resulting class will be dumped (with a TraceClassVisitor
) to the given stream.
* @param args Constructor arguments for the mock.
*
* @return A new mock instance.
*
* @param The type being mocked.
* @see #mock(Class)
* @see #constructMock(Class, Object...)
* @see #constructMock(MoxyEngine, Class, PrintStream, Object...)
* @see MoxyStubber#thenCallRealMethod()
* @since 1.0
*/
@SafeVarargs
public static T constructMock(final Class clz,
final PrintStream trace,
final Object... args) {
return constructMock(ensureMoxyEngine(), clz, trace, args);
}
/**
* Create a mock instance of the given class using the
* supplied {@link MoxyEngine} using a constructor call.
* This method is intended for use when one or more methods
* will be spied (with {@link MoxyStubber#thenCallRealMethod()}.
*
* The mock will be created in the engine's default
* ClassLoader
. For the default engine, this
* is the same ClassLoader
that loaded the
* Moxy framework.
*
* During creation of the mock, this method will also dump the
* generated bytecode, in a debugging format, to the supplied
* PrintStream
(if non-null
).
*
* This method uses a best-fit algorithm to determine
* which constructor to call based on the supplied arguments.
* Primitive types will be boxed/unboxed automatically.
*
* @param engine The {@link MoxyEngine} implementation to use.
* @param clz The Class
to mock.
* @param trace If non-null, the resulting class will be dumped (with a TraceClassVisitor
) to the given stream.
* @param args Constructor arguments for the mock.
*
* @return A new mock instance.
*
* @param The type being mocked.
* @see #mock(Class)
* @see #constructMock(Class, Object...)
* @see #constructMock(Class, PrintStream, Object...)
* @see MoxyStubber#thenCallRealMethod()
* @since 1.0
*/
@SafeVarargs
public static T constructMock(final MoxyEngine engine,
final Class clz,
final PrintStream trace,
final Object... args) {
final Class extends T> mockClz = engine.getMockClass(clz, trace);
final Class>[] argTypes = ArrayUtils.addAll(MOXY_ENGINE_SINGLETON_ARRAY,
(Class>[])Arrays.stream(args).map(Object::getClass).toArray(Class>[]::new));
final Constructor extends T> ctor =
ConstructorUtils.getMatchingAccessibleConstructor(mockClz, argTypes);
if (ctor == null) {
final String typesStr =
"[" + Arrays.stream(argTypes)
.map(Class::getCanonicalName)
.collect(Collectors.joining(", "))
+ "]";
throw new IllegalArgumentException("No constructor matching argument types " + typesStr + " found");
} else {
try {
return ctor.newInstance(ArrayUtils.addAll(new Object[] { engine }, args));
} catch (final InstantiationException e) {
throw new MoxyException("Cannot instantiate mock - instantiation exception (see cause)", e);
} catch (final InvocationTargetException e) {
throw new MoxyException("Cannot instantiate mock - target exception (see cause)", e);
} catch (final IllegalAccessException e) {
throw new MoxyException("[BUG] Cannot instantiate mock - illegal access (see cause)", e);
}
}
}
/**
* Convert the given object into a spy.
*
* This method can be used to convert any Object into a spy. It behaves
* differently depending on whether the passed object is a mock, or
* a standard object:
*
*
* If the given object is a standard object, then a new spy will be
* created that uses {@link MoxyStubber#thenDelegateTo(Object)} behind
* the scenes to delegate all calls to the supplied object.
*
* If the given object is a mock, then all existing stubbing will
* be replaced with {@link MoxyStubber#thenCallRealMethod()}.
*
*
* It is, in the latter case, important to note that this may not be exactly
* the behaviour you expect. You should always convert to a spy before
* applying any additional stubbing.
*
* Note that this will reset all prior stubbing on the given mock.
*
* @param object The object to be spied-upon.
*
* @return The mock, converted to a spy.
* @since 1.0
*
* @param The type of the mock.
*
* @see #spy(Class)
* @see #spy(Class, PrintStream)
* @see #spy(MoxyEngine, Class, PrintStream)
*/
public static T spy(final T object) {
return spy(object, true);
}
/*
* This is used by the spy(Object, boolean) method below.
* It should probably be used with caution elsewhere,
* e.g. it must always be used in context of when or assert...
*/
private static Object pushTypeAppropriatePrimitiveMatcher(final Class> type) {
switch (type.toString()) {
case PRIMITIVE_BYTE_TYPE:
return Matchers.anyByte();
case PRIMITIVE_CHAR_TYPE:
return Matchers.anyChar();
case PRIMITIVE_SHORT_TYPE:
return Matchers.anyShort();
case PRIMITIVE_INT_TYPE:
return Matchers.anyInt();
case PRIMITIVE_LONG_TYPE:
return Matchers.anyLong();
case PRIMITIVE_FLOAT_TYPE:
return Matchers.anyFloat();
case PRIMITIVE_DOUBLE_TYPE:
return Matchers.anyDouble();
case PRIMITIVE_BOOLEAN_TYPE:
return Matchers.anyBool();
default:
return Matchers.any();
}
}
/*
* Convert mock to spy, optionally without resetting.
*
* Used when converting a newly-created mock or
* creating a mock with a non-mock object.
*/
// TODO DRY this
@SuppressWarnings("unchecked")
static T spy(final T original, final boolean doReset) {
if (!isMock(original)) {
// Is real object - spying delegate
final Object newMock = mock(original.getClass());
Arrays.stream(original.getClass().getDeclaredMethods()).forEach(method -> {
if (!Modifier.isStatic(method.getModifiers())) {
method.setAccessible(true);
if (!method.getName().startsWith("__moxy_asm")) {
Moxy.when(() -> method.invoke(newMock,
Arrays.stream(
method.getParameterTypes()).map(
Moxy::pushTypeAppropriatePrimitiveMatcher)
.toArray())
).thenDelegateTo(original);
}
}
});
return (T)newMock;
} else {
// Is mock - make spy
if (doReset) {
resetMock(original);
}
Arrays.stream(original.getClass().getDeclaredMethods()).forEach(method -> {
method.setAccessible(true);
if (!method.getName().startsWith("__moxy_asm")) {
Moxy.when(() -> method.invoke(original,
Arrays.stream(
method.getParameterTypes()).map(
Moxy::pushTypeAppropriatePrimitiveMatcher)
.toArray())
).thenCallRealMethod();
}
});
return original;
}
}
/**
* Create a spy instance of the given class using the
* default {@link MoxyEngine}.
*
* The spy will be created in the engine's default
* ClassLoader
. For the default engine, this
* is the same ClassLoader
that loaded the
* Moxy framework.
*
* Note: This method will not call a constructor
* on the generated instance, which may be more troublesome for spies
* than it is for mocks (as the real methods being called may rely
* on some state initialized by the constructor).
*
* If you require a constructor be called, see
* {@link #constructSpy(Class, Object...)}.
*
* @param clz The Class
to spy.
*
* @return A new spy instance.
*
* @param The type being spied.
* @see #spy(Class, PrintStream)
* @see #spy(MoxyEngine, Class, PrintStream)
* @see #spy(Object)
* @see #constructSpy(Class, Object...)
* @see #mock(Class)
* @see MoxyStubber#thenCallRealMethod()
* @since 1.0
*/
public static T spy(final Class clz) {
return spy(clz, null);
}
/**
* Create a spy instance of the given class using the
* default {@link MoxyEngine}.
*
* The spy will be created in the engine's default
* ClassLoader
. For the default engine, this
* is the same ClassLoader
that loaded the
* Moxy framework.
*
* During creation of the spy, this method will also dump the
* generated bytecode, in a debugging format, to the supplied
* PrintStream
(if non-null
).
*
* Note: This method will not call a constructor
* on the generated instance, which may be more troublesome for spies
* than it is for mocks (as the real methods being called may rely
* on some state initialized by the constructor).
*
* If you require a constructor be called, see
* {@link #constructSpy(Class, PrintStream, Object...)}.
*
* @param clz The Class
to spy.
* @param trace If non-null, the resulting class will be dumped (with a TraceClassVisitor
) to the given stream.
*
* @return A new spy instance.
*
* @param The type being spied.
* @see #spy(Class)
* @see #spy(MoxyEngine, Class, PrintStream)
* @see #spy(Object)
* @see #constructSpy(Class, PrintStream, Object...)
* @see #mock(Class, PrintStream)
* @see MoxyStubber#thenCallRealMethod()
* @since 1.0
*/
public static T spy(final Class clz, final PrintStream trace) {
return spy(ensureMoxyEngine(), clz, trace);
}
/**
* Create a spy instance of the given class using the
* specified {@link MoxyEngine}.
*
* The spy will be created in the engine's default
* ClassLoader
. For the default engine, this
* is the same ClassLoader
that loaded the
* Moxy framework.
*
* During creation of the mock, this method will also dump the
* generated bytecode, in a debugging format, to the supplied
* PrintStream
(if non-null
).
*
* Note: This method will not call a constructor
* on the generated instance, which may be more troublesome for spies
* than it is for mocks (as the real methods being called may rely
* on some state initialized by the constructor).
*
* If you require a constructor be called, see
* {@link #constructSpy(MoxyEngine, Class, PrintStream, Object...)}.
*
* @param engine The {@link MoxyEngine} implementation to use.
* @param clz The Class
to spy.
* @param trace If non-null, the resulting class will be dumped (with a TraceClassVisitor
) to the given stream.
*
* @return A new spy instance.
*
* @param The type being spied.
* @see #spy(Class)
* @see #spy(Class, PrintStream)
* @see #spy(Object)
* @see #constructSpy(MoxyEngine, Class, PrintStream, Object...)
* @see #mock(MoxyEngine, Class, PrintStream)
* @see MoxyStubber#thenCallRealMethod()
* @since 1.0
*/
public static T spy(final MoxyEngine engine, final Class clz, final PrintStream trace) {
return spy(engine.mock(clz, trace));
}
/**
* Create a spy instance of the given class using the
* default {@link MoxyEngine} using a constructor call.
*
* The spy will be created in the engine's default
* ClassLoader
. For the default engine, this
* is the same ClassLoader
that loaded the
* Moxy framework.
*
* This method uses a best-fit algorithm to determine
* which constructor to call based on the supplied arguments.
* Primitive types will be boxed/unboxed automatically.
*
* @param clz The Class
to spy.
* @param args Constructor arguments for the spy.
*
* @return A new spy instance.
*
* @param The type being spied.
* @see #spy(Class)
* @see #spy(Object)
* @see #constructSpy(Class, PrintStream, Object...)
* @see #constructSpy(MoxyEngine, Class, PrintStream, Object...)
* @see #mock(Class)
* @see MoxyStubber#thenCallRealMethod()
* @since 1.0
*/
@SafeVarargs
public static T constructSpy(final Class clz, final Object... args) {
return constructSpy(clz, null, args);
}
/**
* Create a spy instance of the given class using the
* default {@link MoxyEngine} using a constructor call.
*
* The spy will be created in the engine's default
* ClassLoader
. For the default engine, this
* is the same ClassLoader
that loaded the
* Moxy framework.
*
* During creation of the spy, this method will also dump the
* generated bytecode, in a debugging format, to the supplied
* PrintStream
(if non-null
).
*
* This method uses a best-fit algorithm to determine
* which constructor to call based on the supplied arguments.
* Primitive types will be boxed/unboxed automatically.
*
* @param clz The Class
to spy.
* @param trace If non-null, the resulting class will be dumped (with a TraceClassVisitor
) to the given stream.
* @param args Constructor arguments for the spy.
*
* @return A new spy instance.
*
* @param The type being spied.
* @see #spy(Class, PrintStream)
* @see #spy(Object)
* @see #constructSpy(Class, Object...)
* @see #constructSpy(MoxyEngine, Class, PrintStream, Object...)
* @see #mock(Class, PrintStream)
* @see MoxyStubber#thenCallRealMethod()
* @since 1.0
*/
@SafeVarargs
public static T constructSpy(final Class clz,
final PrintStream trace,
final Object... args) {
return constructSpy(ensureMoxyEngine(), clz, trace, args);
}
/**
* Create a spy instance of the given class using the
* supplied {@link MoxyEngine} using a constructor call.
*
* The spy will be created in the engine's default
* ClassLoader
. For the default engine, this
* is the same ClassLoader
that loaded the
* Moxy framework.
*
* During creation of the spy, this method will also dump the
* generated bytecode, in a debugging format, to the supplied
* PrintStream
(if non-null
).
*
* This method uses a best-fit algorithm to determine
* which constructor to call based on the supplied arguments.
* Primitive types will be boxed/unboxed automatically.
*
* @param engine The {@link MoxyEngine} implementation to use.
* @param clz The Class
to spy.
* @param trace If non-null, the resulting class will be dumped (with a TraceClassVisitor
) to the given stream.
* @param args Constructor arguments for the spy.
*
* @return A new spy instance.
*
* @param The type being spied.
* @see #spy(MoxyEngine, Class, PrintStream)
* @see #spy(Object)
* @see #constructSpy(Class, Object...)
* @see #constructSpy(Class, PrintStream, Object...)
* @see #mock(MoxyEngine, Class, PrintStream)
* @see MoxyStubber#thenCallRealMethod()
* @since 1.0
*/
@SafeVarargs
public static T constructSpy(final MoxyEngine engine,
final Class clz,
final PrintStream trace,
final Object... args) {
final T mock = constructMock(engine, clz, trace, args);
return spy(mock, false);
}
/**
* Determines whether the supplied class is a mock class.
*
* How this determination is made is engine-dependent, but may
* rely on the fact that the class has the (mandatory)
* {@link com.roscopeco.moxy.api.MoxyMock} annotation.
*
* @param clz The class to query.
*
* @return true
if the class is a mock, false
otherwise.
*
* @since 1.0
*/
public static boolean isMock(final Class> clz) {
return isMock(ensureMoxyEngine(), clz);
}
/**
* Determines whether the supplied object is a mock instance.
*
* How this determination is made is engine-dependent, but may
* rely on the fact that the class has the (mandatory)
* {@link com.roscopeco.moxy.api.MoxyMock} annotation.
*
* @param obj The object to query.
*
* @return true
if the object is a mock, false
otherwise.
*
* @since 1.0
*/
public static boolean isMock(final Object obj) {
return isMock(ensureMoxyEngine(), obj);
}
/**
* Reset the supplied mock, removing all stubbing that was previously applied.
*
* Note that this does not reset previous
* invocation data for the mock. If you wish to reset that, see {@link MoxyEngine#reset()}.
*
* @param mock The mock to reset.
*
* @see MoxyEngine#reset()
* @see Moxy#resetMock(MoxyEngine, Object)
* @see MoxyEngine#resetMock(Object)
* @since 1.0
*/
public static void resetMock(final Object mock) {
resetMock(ensureMoxyEngine(), mock);
}
/**
* Reset the supplied mock using the supplied engine, removing all stubbing
* that was previously applied.
*
* Note that this does not not reset previous
* invocation data for the mock. If you wish to reset that, see {@link MoxyEngine#reset()}.
*
* @param engine The {@link MoxyEngine} to use.
* @param mock The mock to reset.
*
* @see MoxyEngine#reset()
* @see MoxyEngine#resetMock(Object)
* @since 1.0
*/
public static void resetMock(final MoxyEngine engine, final Object mock) {
engine.resetMock(mock);
}
/**
* Determines whether the supplied class is a mock class in the context
* of the supplied {@link MoxyEngine}.
*
* How this determination is made is engine-dependent.
*
* @param engine The {@link MoxyEngine} implementation to use.
* @param clz The class to query.
*
* @return true
if the class is a mock, false
otherwise.
*
* @since 1.0
*/
public static boolean isMock(final MoxyEngine engine, final Class> clz) {
return engine.isMock(clz);
}
/**
* Determines whether the supplied class is a mock class in the context
* of the supplied {@link MoxyEngine}.
*
* How this determination is made is engine-dependent.
*
* @param engine The {@link MoxyEngine} implementation to use.
* @param obj The object to query.
*
* @return true
if the object is a mock, false
otherwise.
*
* @since 1.0
*/
public static boolean isMock(final MoxyEngine engine, final Object obj) {
return engine.isMock(obj);
}
/**
* Starts stubbing of the mock invocation in the supplied lambda
* expression using the currently-set (or default) {@link MoxyEngine}.
*
* The invocation of the mock is used to determine which method
* and argument combination is to be stubbed and is not counted
* toward mock invocation, return or throw counters.
*
* Example usage:
*
*
* Moxy.when(() -> mock.someMethod("one", "two")).thenReturn("three");
*
*
* The arguments passed to the mock within the lambda may be either
* immediate arguments, other mocks, or argument matchers (see {@link Matchers}).
*
* This method begins a new chain of stubbing for the given invocation -
* Any prior stubbing applied is discarded, to be replaced by the
* stubbing applied on the returned {@link MoxyStubber}.
*
* See {@link MoxyStubber} for details on the stubbing methods
* available.
*
* @param invocation A lambda that will invoke the method to be stubbed.
*
* @return A {@link MoxyStubber} that will stub the given method.
*
* @param The type being stubbed (return type of the mock method).
* @see #when(InvocationRunnable)
* @see #when(MoxyEngine, InvocationSupplier)
* @see MoxyStubber
* @since 1.0
*/
public static MoxyStubber when(final InvocationSupplier invocation) {
return when(ensureMoxyEngine(), invocation);
}
/**
* Starts stubbing of the mock invocation in the supplied lambda
* expression using the currently-set (or default) {@link MoxyEngine}.
*
* The Java compiler will automatically select this overload of
* the when
method when the method to be stubbed is
* void.
*
* The invocation of the mock is used to determine which method
* and argument combination is to be stubbed and is not counted
* toward mock invocation, return or throw counters.
*
* Example usage:
*
*
* Moxy.when(() -> mock.voidMethod("one", "two")).thenThrow("three");
*
*
* The arguments passed to the mock within the lambda may be either
* immediate arguments, other mocks, or argument matchers (see {@link Matchers}).
*
* This method begins a new chain of stubbing for the given invocation -
* Any prior stubbing applied is discarded, to be replaced by the
* stubbing applied on the returned {@link MoxyVoidStubber}.
*
* See {@link MoxyStubber} for details on the stubbing methods
* available.
*
* @param invocation A lambda that will invoke the method to be stubbed.
*
* @return A {@link MoxyStubber} that will stub the given method.
*
* @see #when(InvocationSupplier)
* @see #when(MoxyEngine, InvocationRunnable)
* @see MoxyStubber
* @since 1.0
*/
public static MoxyVoidStubber when(final InvocationRunnable invocation) {
return when(ensureMoxyEngine(), invocation);
}
/**
* Starts stubbing of the mock invocation in the supplied lambda
* expression using the specified {@link MoxyEngine}.
*
* The invocation of the mock is used to determine which method
* and argument combination is to be stubbed and is not counted
* toward mock invocation, return or throw counters.
*
* Example usage:
*
*
* Moxy.when(engine, () -> mock.someMethod("one", "two")).thenReturn("three");
*
*
* The arguments passed to the mock within the lambda may be either
* immediate arguments, other mocks, or argument matchers (see {@link Matchers}).
*
* See {@link MoxyStubber} for details on the stubbing methods
* available.
*
* @param engine The {@link MoxyEngine} implementation to use.
* @param invocation A lambda that will invoke the method to be stubbed.
*
* @return A {@link MoxyStubber} that will stub the given method.
*
* @param The type being stubbed (return type of the mocked method).
* @see #when(MoxyEngine, InvocationRunnable)
* @see #when(InvocationSupplier)
* @see MoxyStubber
* @since 1.0
*/
public static MoxyStubber when(final MoxyEngine engine, final InvocationSupplier invocation) {
return engine.when(invocation);
}
/**
* Starts stubbing of the mock invocation in the supplied lambda
* expression using the specified {@link MoxyEngine}.
*
* The Java compiler will automatically select this overload of
* the when
method when the method to be stubbed is
* void.
*
* The invocation of the mock is used to determine which method
* and argument combination is to be stubbed and is not counted
* toward mock invocation, return or throw counters.
*
* Example usage:
*
*
* Moxy.when(() -> mock.voidMethod(engine, "one", "two")).thenThrow("three");
*
*
* The arguments passed to the mock within the lambda may be either
* immediate arguments, other mocks, or argument matchers (see {@link Matchers}).
*
* See {@link MoxyStubber} for details on the stubbing methods
* available.
*
* @param engine The {@link MoxyEngine} implementation to use.
* @param invocation A lambda that will invoke the method to be stubbed.
*
* @return A {@link MoxyStubber} that will stub the given method.
*
* @see #when(InvocationSupplier)
* @see #when(MoxyEngine, InvocationRunnable)
* @see MoxyStubber
* @since 1.0
*/
public static MoxyVoidStubber when(final MoxyEngine engine, final InvocationRunnable invocation) {
return engine.when(invocation);
}
/**
* Starts verification of the mock invocation in the supplied lambda
* expression using the currently-set (or default) {@link MoxyEngine}.
*
* The invocation of the mock is used to determine which method
* and argument combination is to be verified and is not counted
* toward mock invocation, return or throw counters.
*
* Example usage:
*
*
* Moxy.assertMock(() -> mock.voidMethod(engine, "one", "two")).wasCalled();
*
*
* The arguments passed to the mock within the lambda may be either
* immediate arguments, other mocks, or argument matchers (see {@link Matchers}).
*
* See {@link MoxyStubber} for details on the stubbing methods
* available.
*
* @param invocation A lambda that will invoke the method to be stubbed.
*
* @return A {@link MoxyVerifier} that will verify the given method.
*
* @see #assertMock(MoxyEngine, InvocationRunnable)
* @see MoxyVerifier
* @since 1.0
*/
public static MoxyVerifier assertMock(final InvocationRunnable invocation) {
return assertMock(ensureMoxyEngine(), invocation);
}
/**
* Starts verification of the mock invocation in the supplied lambda
* expression using the specified {@link MoxyEngine}.
*
* The invocation of the mock is used to determine which method
* and argument combination is to be verified and is not counted
* toward mock invocation, return or throw counters.
*
* Example usage:
*
*
* Moxy.assertMock(() -> mock.voidMethod(engine, "one", "two")).wasCalled();
*
*
* The arguments passed to the mock within the lambda may be either
* immediate arguments, other mocks, or argument matchers (see {@link Matchers}).
*
* See {@link MoxyStubber} for details on the stubbing methods
* available.
*
* @param engine The {@link MoxyEngine} implementation to use.
* @param invocation A lambda that will invoke the method to be stubbed.
*
* @return A {@link MoxyVerifier} that will verify the given method.
*
* @see #assertMock(InvocationRunnable)
* @see MoxyVerifier
* @since 1.0
*/
public static MoxyVerifier assertMock(final MoxyEngine engine, final InvocationRunnable invocation) {
return engine.assertMock(invocation);
}
/**
* Starts verification of one or more mock invocations at the same
* time. This allows multiple invocations to be checked together
* to ensure ordering, for example.
*
* This method uses the current default {@link MoxyEngine}.
*
*
The invocation of the mock is used to determine which method
* and argument combination is to be verified and is not counted
* toward mock invocation, return or throw counters.
*
* Example usage:
*
*
* engine.assertMocks(() -> {
* mock.voidMethod(engine, "one", "two")).wasCalled();
* mock.anotherMethod();
*
* anotherMock.someMethod("five");
*
* mock.finalMethod("Bees");
* })
* .wereAllCalledOnce()
* .inThatOrder();
*
*
* The arguments passed to the mocks within the lambda may be either
* immediate arguments, other mocks, or argument matchers (see {@link Matchers}).
*
* See {@link MoxyMultiVerifier} for details on the verifying methods
* available.
*
* @param invocation A lambda that will invoke the methods to be verified.
* @return A {@link MoxyMultiVerifier} that will verify the invocations.
*
* @see #assertMock(InvocationRunnable)
* @since 1.0
*/
public static MoxyMultiVerifier assertMocks(final InvocationRunnable invocation) {
return assertMocks(ensureMoxyEngine(), invocation);
}
/**
* Starts verification of one or more mock invocations at the same
* time. This allows multiple invocations to be checked together
* to ensure ordering, for example.
*
* This method uses the supplied {@link MoxyEngine}.
*
*
The invocation of the mock is used to determine which method
* and argument combination is to be verified and is not counted
* toward mock invocation, return or throw counters.
*
* Example usage:
*
*
* engine.assertMocks(() -> {
* mock.voidMethod(engine, "one", "two")).wasCalled();
* mock.anotherMethod();
*
* anotherMock.someMethod("five");
*
* mock.finalMethod("Bees");
* })
* .wereAllCalledOnce()
* .inThatOrder();
*
*
* The arguments passed to the mocks within the lambda may be either
* immediate arguments, other mocks, or argument matchers (see {@link Matchers}).
*
* See {@link MoxyMultiVerifier} for details on the verifying methods
* available.
*
* @param engine The {@link MoxyEngine} to use.
* @param invocation A lambda that will invoke the methods to be verified.
* @return A {@link MoxyMultiVerifier} that will verify the invocations.
*
* @see #assertMock(InvocationRunnable)
* @since 1.0
*/
public static MoxyMultiVerifier assertMocks(final MoxyEngine engine,
final InvocationRunnable invocation) {
return engine.assertMocks(invocation);
}
}