Skip to content

Commit

Permalink
Isolation from package-protected spring boot test code (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
antoinemeyer authored Apr 10, 2021
1 parent 44dbbb5 commit b7ff305
Show file tree
Hide file tree
Showing 46 changed files with 222 additions and 177 deletions.
2 changes: 1 addition & 1 deletion README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Simply include the maven dependency (from central maven) to start using @MockInB
<dependency>
<groupId>com.teketik</groupId>
<artifactId>mock-in-bean</artifactId>
<version>boot2-v1.0</version>
<version>boot2-v1.1</version>
<scope>test</scope>
</dependency>
```
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.teketik</groupId>
<artifactId>mock-in-bean</artifactId>
<version>boot2-v1.0-SNAPSHOT</version>
<version>boot2-v1.1-SNAPSHOT</version>
<name>Mock in Bean</name>
<description>Surgically Inject Mockito Mock/Spy in Spring Beans</description>
<url>https://github.com/antoinemeyer/mock-in-bean</url>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.springframework.boot.test.mock.mockito;
package com.teketik.test.mockinbean;

import org.springframework.test.context.TestContext;

Expand All @@ -8,7 +8,7 @@ class BeanFieldState extends FieldState {

private Object bean;

public BeanFieldState(Object bean, Field field, Object originalValue, DefinitionFacade definition) {
public BeanFieldState(Object bean, Field field, Object originalValue, Definition definition) {
super(field, originalValue, definition);
this.bean = bean;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.springframework.boot.test.mock.mockito;
package com.teketik.test.mockinbean;

import org.springframework.context.ApplicationContext;
import org.springframework.lang.Nullable;
Expand Down
66 changes: 66 additions & 0 deletions src/main/java/com/teketik/test/mockinbean/Definition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.teketik.test.mockinbean;

import org.mockito.Mock;
import org.mockito.Spy;
import org.springframework.core.ResolvableType;

import java.util.Objects;

/**
* <p>Definition of a {@link Mock mock} or a {@link Spy spy}.
* <p>Corresponding entity can be created using {@link #create(Object)}.
* @author Antoine Meyer
*/
abstract class Definition {

protected final String name;
protected final ResolvableType resolvableType;

Definition(String name, ResolvableType resolvableType) {
super();
this.name = name;
this.resolvableType = resolvableType;
}

String getName() {
return name;
}

ResolvableType getResolvableType() {
return resolvableType;
}

/**
* Creates a mock or a spy of the provided original value.
* @param <T>
* @param originalValue
* @return
*/
abstract <T> T create(Object originalValue);

@Override
public int hashCode() {
return Objects.hash(name, resolvableType);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Definition other = (Definition) obj;
return Objects.equals(name, other.name) && Objects.equals(resolvableType, other.resolvableType);
}

@Override
public String toString() {
return "Definition [name=" + name + ", resolvableType=" + resolvableType + "]";
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.springframework.boot.test.mock.mockito;
package com.teketik.test.mockinbean;

import org.springframework.lang.Nullable;
import org.springframework.test.context.TestContext;
Expand All @@ -12,14 +12,14 @@ abstract class FieldState {
@Nullable
final Object originalValue;

final DefinitionFacade definition;
final Definition definition;

public FieldState(Field targetField, Object originalValue, DefinitionFacade definition) {
public FieldState(Field targetField, Object originalValue, Definition definition) {
this.field = targetField;
this.originalValue = originalValue;
this.definition = definition;
}

public abstract Object resolveTarget(TestContext testContext);
abstract Object resolveTarget(TestContext testContext);

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
package org.springframework.boot.test.mock.mockito;

import com.teketik.test.mockinbean.MockInBean;
import com.teketik.test.mockinbean.SpyInBean;
package com.teketik.test.mockinbean;

import org.springframework.lang.Nullable;

Expand All @@ -16,7 +13,7 @@ class InBeanDefinition {
@Nullable
final String name;

public InBeanDefinition(Class<?> clazz, String name) {
InBeanDefinition(Class<?> clazz, String name) {
super();
this.clazz = clazz;
this.name = name;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
package org.springframework.boot.test.mock.mockito;

import com.teketik.test.mockinbean.MockInBean;
import com.teketik.test.mockinbean.SpyInBean;
package com.teketik.test.mockinbean;

import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.MergedAnnotation;
Expand All @@ -23,13 +20,13 @@
import java.util.Set;

/**
* <p>Similar to {@link DefinitionsParser} but handles {@link MockInBean} and {@link SpyInBean}.
* <p>Similar to {@link org.springframework.boot.test.mock.mockito.DefinitionsParser} but handles {@link MockInBean} and {@link SpyInBean}.
* <p>Every mock/spy {@link Definition} maps to one or more {@link InBeanDefinition}.
* @see DefinitionsParser
*/
class InBeanDefinitionsParser {

private final Map<DefinitionFacade, List<InBeanDefinition>> definitions = new HashMap<DefinitionFacade, List<InBeanDefinition>>();
private final Map<Definition, List<InBeanDefinition>> definitions = new HashMap<Definition, List<InBeanDefinition>>();

void parse(Class<?> source) {
ReflectionUtils.doWithFields(source, (field) -> parseField(field, source));
Expand All @@ -51,33 +48,35 @@ private void parseMockInBeanAnnotation(MockInBean annotation, Field field, Class
final Set<ResolvableType> typesToMock = getOrDeduceTypes(field, source);
Assert.state(!typesToMock.isEmpty(), () -> "Unable to deduce type to mock from " + field);
for (ResolvableType typeToMock : typesToMock) {
final MockDefinition mockDefinition = new MockDefinition(field.getName(), typeToMock, new Class[] {}, null, false, MockReset.AFTER, QualifierDefinition.forElement(field));
InBeanDefinition inBeanDefinition = new InBeanDefinition(
final Definition definition = new MockDefinition(
field.getName(),
typeToMock
);
final InBeanDefinition inBeanDefinition = new InBeanDefinition(
annotation.value(),
StringUtils.isEmpty(annotation.name()) ? null : annotation.name()
);
addDefinition(mockDefinition, inBeanDefinition);
addDefinition(definition, inBeanDefinition);
}
}

private void parseSpyInBeanAnnotation(SpyInBean annotation, Field field, Class<?> source) {
final Set<ResolvableType> typesToSpy = getOrDeduceTypes(field, source);
Assert.state(!typesToSpy.isEmpty(), () -> "Unable to deduce type to spy from " + field);
for (ResolvableType typeToSpy : typesToSpy) {
final SpyDefinition spyDefinition = new SpyDefinition(field.getName(), typeToSpy, MockReset.AFTER, true, QualifierDefinition.forElement(field));
InBeanDefinition inBeanDefinition = new InBeanDefinition(
final Definition definition = new SpyDefinition(
field.getName(),
typeToSpy
);
final InBeanDefinition inBeanDefinition = new InBeanDefinition(
annotation.value(),
StringUtils.isEmpty(annotation.name()) ? null : annotation.name()
);
addDefinition(spyDefinition, inBeanDefinition);
addDefinition(definition, inBeanDefinition);
}
}

private void addDefinition(Definition definition, InBeanDefinition inBeanDefinition) {
addDefinition(new DefinitionFacade(definition), inBeanDefinition);
}

private void addDefinition(DefinitionFacade definition, InBeanDefinition inBeanDefinition) {
List<InBeanDefinition> inBeanBaseDefinitions = definitions.get(definition);
if (inBeanBaseDefinitions == null) {
inBeanBaseDefinitions = new LinkedList<InBeanDefinition>();
Expand All @@ -99,7 +98,7 @@ private Set<ResolvableType> getOrDeduceTypes(AnnotatedElement element, Class<?>
return types;
}

public Map<DefinitionFacade, List<InBeanDefinition>> getDefinitions() {
public Map<Definition, List<InBeanDefinition>> getDefinitions() {
return Collections.unmodifiableMap(definitions);
}

Expand Down
22 changes: 22 additions & 0 deletions src/main/java/com/teketik/test/mockinbean/MockDefinition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.teketik.test.mockinbean;

import static org.mockito.Mockito.mock;

import org.mockito.MockSettings;
import org.springframework.boot.test.mock.mockito.MockReset;
import org.springframework.core.ResolvableType;

class MockDefinition extends Definition {

MockDefinition(String name, ResolvableType type) {
super(name, type);
}

@Override
<T> T create(Object originalValue) {
MockSettings settings = MockReset.withSettings(MockReset.AFTER);
settings.name(name);
return (T) mock(resolvableType.resolve(), settings);
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.springframework.boot.test.mock.mockito;
package com.teketik.test.mockinbean;

import org.mockito.Mock;
import org.mockito.Spy;
Expand Down Expand Up @@ -38,9 +38,9 @@ public void beforeTestClass(TestContext testContext) throws Exception {
parser.parse(testContext.getTestClass());
final Set<Field> visitedFields = new HashSet<>();
final LinkedList<FieldState> originalValues = new LinkedList<>();
for (Entry<DefinitionFacade, List<InBeanDefinition>> definitionToInbeans : parser.getDefinitions().entrySet()) {
final DefinitionFacade definition = definitionToInbeans.getKey();
final Class<?> mockOrSpyType = definition.getType();
for (Entry<Definition, List<InBeanDefinition>> definitionToInbeans : parser.getDefinitions().entrySet()) {
final Definition definition = definitionToInbeans.getKey();
final Class<?> mockOrSpyType = (Class<?>) definition.getResolvableType().getType();
Field beanField = null;
for (InBeanDefinition inBeanDefinition : definitionToInbeans.getValue()) {
beanField = BeanUtils.findField(inBeanDefinition.clazz, definition.getName(), mockOrSpyType);
Expand Down Expand Up @@ -79,12 +79,12 @@ public void beforeTestClass(TestContext testContext) throws Exception {
*/
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
final Map<DefinitionFacade, Object> mockOrSpys = new HashMap<>();
final Map<Definition, Object> mockOrSpys = new HashMap<>();
((LinkedList<FieldState>) testContext.getAttribute(ORIGINAL_VALUES_ATTRIBUTE_NAME))
.forEach(fieldState -> {
Object mockOrSpy = mockOrSpys.get(fieldState.definition);
if (mockOrSpy == null) {
mockOrSpy = fieldState.definition.makeMockOrSpy(fieldState.originalValue);
mockOrSpy = fieldState.definition.create(fieldState.originalValue);
mockOrSpys.put(fieldState.definition, mockOrSpy);
}
ReflectionUtils.setField(
Expand Down
27 changes: 27 additions & 0 deletions src/main/java/com/teketik/test/mockinbean/SpyDefinition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.teketik.test.mockinbean;

import org.mockito.MockSettings;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.MockReset;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;

class SpyDefinition extends Definition {

SpyDefinition(String name, ResolvableType type) {
super(name, type);
}

@Override
<T> T create(Object originalValue) {
Assert.notNull(originalValue, "originalValue must not be null");
Assert.isInstanceOf(this.resolvableType.resolve(), originalValue);
Assert.state(!Mockito.mockingDetails(originalValue).isSpy(), "originalValue is already a spy");
MockSettings settings = MockReset.withSettings(MockReset.AFTER);
settings.name(name);
settings.spiedInstance(originalValue);
settings.defaultAnswer(Mockito.CALLS_REAL_METHODS);
return (T) Mockito.mock(originalValue.getClass(), settings);
}

}
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package org.springframework.boot.test.mock.mockito;
package com.teketik.test.mockinbean;

import org.springframework.test.context.TestContext;

import java.lang.reflect.Field;

class TestFieldState extends FieldState {

public TestFieldState(Field targetField, Object originalValue, DefinitionFacade definition) {
TestFieldState(Field targetField, Object originalValue, Definition definition) {
super(targetField, originalValue, definition);
}

@Override
public Object resolveTarget(TestContext testContext) {
Object resolveTarget(TestContext testContext) {
return testContext.getTestInstance();
}

Expand Down
Loading

0 comments on commit b7ff305

Please sign in to comment.