package org.jooby.test;
import static java.util.Objects.requireNonNull;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createStrictMock;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.powermock.api.easymock.PowerMock;
import com.google.common.base.Throwables;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.primitives.Primitives;
/**
* Utility test class for mocks. Internal use only.
*
* @author edgar
*/
@SuppressWarnings({"rawtypes", "unchecked" })
public class MockUnit {
public class ConstructorBuilder<T> {
private Class[] types;
private Class<T> type;
public ConstructorBuilder(final Class<T> type) {
this.type = type;
}
public T build(final Object... args) throws Exception {
mockClasses.add(type);
if (types == null) {
types = Arrays.asList(type.getDeclaredConstructors())
.stream()
.filter(c -> {
Class<?>[] types = c.getParameterTypes();
if (types.length == args.length) {
for (int i = 0; i < types.length; i++) {
if (!types[i].isInstance(args[i])
&& !Primitives.wrap(types[i]).isInstance(args[i])) {
return false;
}
}
return true;
}
return false;
}).map(c -> c.getParameterTypes())
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unable to find parameter types"));
}
T mock = PowerMock.createMockAndExpectNew(type, types, args);
partialMocks.add(mock);
return mock;
}
public ConstructorBuilder<T> args(final Class... types) {
this.types = types;
return this;
}
}
public interface Block {
public void run(MockUnit unit) throws Throwable;
}
private List<Object> mocks = new LinkedList<>();
private List<Object> partialMocks = new LinkedList<>();
private Multimap<Class, Object> globalMock = ArrayListMultimap.create();
private Map<Class, List<Capture<Object>>> captures = new LinkedHashMap<>();
private Set<Class> mockClasses = new LinkedHashSet<>();
private List<Block> blocks = new LinkedList<>();
public MockUnit(final Class... types) {
this(false, types);
}
public MockUnit(final boolean strict, final Class... types) {
Arrays.stream(types).forEach(type -> {
registerMock(type);
});
}
public <T> T capture(final Class<T> type) {
Capture<Object> capture = new Capture<>();
List<Capture<Object>> captures = this.captures.get(type);
if (captures == null) {
captures = new ArrayList<>();
this.captures.put(type, captures);
}
captures.add(capture);
return (T) EasyMock.capture(capture);
}
public <T> List<T> captured(final Class<T> type) {
List<Capture<Object>> captureList = this.captures.get(type);
List<T> result = new LinkedList<>();
captureList.stream().filter(Capture::hasCaptured).forEach(it -> result.add((T) it.getValue()));
return result;
}
public <T> Class<T> mockStatic(final Class<T> type) {
if (mockClasses.add(type)) {
PowerMock.mockStatic(type);
mockClasses.add(type);
}
return type;
}
public <T> Class<T> mockStaticPartial(final Class<T> type, final String... names) {
if (mockClasses.add(type)) {
PowerMock.mockStaticPartial(type, names);
mockClasses.add(type);
}
return type;
}
public <T> T partialMock(final Class<T> type, final String... methods) {
T mock = PowerMock.createPartialMock(type, methods);
partialMocks.add(mock);
return mock;
}
public <T> T partialMock(final Class<T> type, final String method, final Class<?> firstArg) {
T mock = PowerMock.createPartialMock(type, method, firstArg);
partialMocks.add(mock);
return mock;
}
public <T> T partialMock(final Class<T> type, final String method, final Class t1,
final Class t2) {
T mock = PowerMock.createPartialMock(type, method, t1, t2);
partialMocks.add(mock);
return mock;
}
public <T> T mock(final Class<T> type) {
return mock(type, false);
}
public <T> T powerMock(final Class<T> type) {
T mock = PowerMock.createMock(type);
partialMocks.add(mock);
return mock;
}
public <T> T mock(final Class<T> type, final boolean strict) {
if (Modifier.isFinal(type.getModifiers())) {
T mock = PowerMock.createMock(type);
partialMocks.add(mock);
return mock;
} else {
T mock = strict ? createStrictMock(type) : createMock(type);
mocks.add(mock);
return mock;
}
}
public <T> T registerMock(final Class<T> type) {
T mock = mock(type);
globalMock.put(type, mock);
return mock;
}
public <T> T registerMock(final Class<T> type, final T mock) {
globalMock.put(type, mock);
return mock;
}
public <T> T get(final Class<T> type) {
try {
List<Object> collection = (List<Object>) requireNonNull(globalMock.get(type));
T m = (T) collection.get(collection.size() - 1);
return m;
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalStateException("Not found: " + type);
}
}
public <T> T first(final Class<T> type) {
List<Object> collection = (List<Object>) requireNonNull(globalMock.get(type),
"Mock not found: " + type);
return (T) collection.get(0);
}
public MockUnit expect(final Block block) {
blocks.add(requireNonNull(block, "A block is required."));
return this;
}
public MockUnit run(final Block... blocks) throws Exception {
for (Block block : this.blocks) {
try {
block.run(this);
} catch (Exception | AssertionError ex) {
throw ex;
} catch (Throwable ex) {
Throwables.propagate(ex);
}
}
mockClasses.forEach(PowerMock::replay);
partialMocks.forEach(PowerMock::replay);
mocks.forEach(EasyMock::replay);
for (Block main : blocks) {
try {
main.run(this);
} catch (Exception | AssertionError ex) {
throw ex;
} catch (Throwable ex) {
Throwables.propagate(ex);
}
}
mocks.forEach(EasyMock::verify);
partialMocks.forEach(PowerMock::verify);
mockClasses.forEach(PowerMock::verify);
return this;
}
public <T> T mockConstructor(final Class<T> type, final Class<?>[] paramTypes,
final Object... args) throws Exception {
mockClasses.add(type);
T mock = PowerMock.createMockAndExpectNew(type, paramTypes, args);
partialMocks.add(mock);
return mock;
}
public <T> T mockConstructor(final Class<T> type, final Object... args) throws Exception {
Class[] types = new Class[args.length];
for (int i = 0; i < types.length; i++) {
types[i] = args[i].getClass();
}
return mockConstructor(type, types, args);
}
public <T> ConstructorBuilder<T> constructor(final Class<T> type) {
return new ConstructorBuilder<T>(type);
}
}