Skip to content

Commit e9521ab

Browse files
#patch: Enable snapshot-testing in a superclass
Tests can now use `Expect` defined in a superclass
1 parent ddfd6ee commit e9521ab

File tree

12 files changed

+162
-22
lines changed

12 files changed

+162
-22
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package au.com.origin.snapshots.utils;
2+
3+
import java.lang.reflect.Field;
4+
import java.lang.reflect.Modifier;
5+
import java.util.Optional;
6+
import java.util.function.Predicate;
7+
import lombok.experimental.UtilityClass;
8+
9+
@UtilityClass
10+
public class ReflectionUtils {
11+
12+
/**
13+
* Find {@link Field} by given predicate.
14+
*
15+
* <p>Invoke the given predicate on all fields in the target class, going up the class hierarchy
16+
* to get all declared fields.
17+
*
18+
* @param clazz the target class to analyze
19+
* @param predicate the predicate
20+
* @return the field or empty optional
21+
*/
22+
public static Optional<Field> findFieldByPredicate(
23+
final Class<?> clazz, final Predicate<Field> predicate) {
24+
Class<?> targetClass = clazz;
25+
26+
do {
27+
final Field[] fields = targetClass.getDeclaredFields();
28+
for (final Field field : fields) {
29+
if (!predicate.test(field)) {
30+
continue;
31+
}
32+
return Optional.of(field);
33+
}
34+
targetClass = targetClass.getSuperclass();
35+
} while (targetClass != null && targetClass != Object.class);
36+
37+
return Optional.empty();
38+
}
39+
40+
public static void makeAccessible(final Field field) {
41+
if ((!Modifier.isPublic(field.getModifiers())
42+
|| !Modifier.isPublic(field.getDeclaringClass().getModifiers())
43+
|| Modifier.isFinal(field.getModifiers()))
44+
&& !field.isAccessible()) {
45+
field.setAccessible(true);
46+
}
47+
}
48+
}

java-snapshot-testing-core/src/test/java/au/com/origin/snapshots/SnapshotHeaders.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,5 @@ public Snapshot apply(Object object, SnapshotSerializerContext snapshotSerialize
5555
snapshotSerializerContext.getHeader().put("custom2", "anything2");
5656
return super.apply(object, snapshotSerializerContext);
5757
}
58-
};
58+
}
5959
}

java-snapshot-testing-junit4/src/main/java/au/com/origin/snapshots/junit4/SharedSnapshotHelpers.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import au.com.origin.snapshots.config.PropertyResolvingSnapshotConfig;
55
import au.com.origin.snapshots.config.SnapshotConfig;
66
import au.com.origin.snapshots.config.SnapshotConfigInjector;
7+
import au.com.origin.snapshots.utils.ReflectionUtils;
78
import java.lang.reflect.Method;
89
import java.util.Arrays;
910
import org.junit.runner.Description;
@@ -14,13 +15,13 @@ class SharedSnapshotHelpers implements SnapshotConfigInjector {
1415

1516
public void injectExpectInstanceVariable(
1617
SnapshotVerifier snapshotVerifier, Method testMethod, Object testInstance) {
17-
Arrays.stream(testInstance.getClass().getDeclaredFields())
18-
.filter(it -> it.getType() == Expect.class)
19-
.findFirst()
18+
19+
ReflectionUtils.findFieldByPredicate(
20+
testInstance.getClass(), (field) -> field.getType() == Expect.class)
2021
.ifPresent(
21-
field -> {
22+
(field) -> {
2223
Expect expect = Expect.of(snapshotVerifier, testMethod);
23-
field.setAccessible(true);
24+
ReflectionUtils.makeAccessible(field);
2425
try {
2526
field.set(testInstance, expect);
2627
} catch (IllegalAccessException e) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package au.com.origin.snapshots;
2+
3+
import au.com.origin.snapshots.junit4.SnapshotRunner;
4+
import org.junit.Test;
5+
import org.junit.experimental.runners.Enclosed;
6+
import org.junit.runner.RunWith;
7+
8+
@RunWith(Enclosed.class)
9+
public class BaseClassTest {
10+
11+
static class TestBase {
12+
Expect expect;
13+
}
14+
15+
@RunWith(SnapshotRunner.class)
16+
public static class NestedClass extends TestBase {
17+
18+
@Test
19+
public void helloWorldTest() {
20+
expect.toMatchSnapshot("Hello World");
21+
}
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
au.com.origin.snapshots.BaseClassTest$NestedClass.helloWorldTest=[
2+
Hello World
3+
]

java-snapshot-testing-junit5/src/main/java/au/com/origin/snapshots/junit5/SnapshotExtension.java

+12-7
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@
77
import au.com.origin.snapshots.config.SnapshotConfigInjector;
88
import au.com.origin.snapshots.exceptions.SnapshotMatchException;
99
import au.com.origin.snapshots.logging.LoggingHelper;
10+
import au.com.origin.snapshots.utils.ReflectionUtils;
1011
import java.lang.reflect.Field;
11-
import java.util.Arrays;
1212
import lombok.extern.slf4j.Slf4j;
13-
import org.junit.jupiter.api.extension.*;
13+
import org.junit.jupiter.api.extension.AfterAllCallback;
14+
import org.junit.jupiter.api.extension.BeforeAllCallback;
15+
import org.junit.jupiter.api.extension.BeforeEachCallback;
16+
import org.junit.jupiter.api.extension.ExtensionContext;
17+
import org.junit.jupiter.api.extension.ParameterContext;
18+
import org.junit.jupiter.api.extension.ParameterResolutionException;
19+
import org.junit.jupiter.api.extension.ParameterResolver;
1420
import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor;
1521
import org.junit.jupiter.engine.descriptor.ClassTestDescriptor;
1622

@@ -104,13 +110,12 @@ public Object resolveParameter(
104110
@Override
105111
public void beforeEach(ExtensionContext context) {
106112
if (context.getTestInstance().isPresent() && context.getTestMethod().isPresent()) {
107-
Arrays.stream(context.getTestClass().get().getDeclaredFields())
108-
.filter(it -> it.getType() == Expect.class)
109-
.findFirst()
113+
ReflectionUtils.findFieldByPredicate(
114+
context.getTestClass().get(), (field) -> field.getType() == Expect.class)
110115
.ifPresent(
111-
field -> {
116+
(field) -> {
112117
Expect expect = Expect.of(snapshotVerifier, context.getTestMethod().get());
113-
field.setAccessible(true);
118+
ReflectionUtils.makeAccessible(field);
114119
try {
115120
field.set(context.getTestInstance().get(), expect);
116121
} catch (IllegalAccessException e) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package au.com.origin.snapshots;
2+
3+
import au.com.origin.snapshots.junit5.SnapshotExtension;
4+
import org.junit.jupiter.api.Nested;
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.api.extension.ExtendWith;
7+
8+
@ExtendWith({SnapshotExtension.class})
9+
public class BaseClassTest {
10+
11+
class TestBase {
12+
Expect expect;
13+
}
14+
15+
@Nested
16+
@ExtendWith(SnapshotExtension.class)
17+
class NestedClass extends TestBase {
18+
19+
@Test
20+
public void helloWorldTest() {
21+
expect.toMatchSnapshot("Hello World");
22+
}
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
au.com.origin.snapshots.BaseClassTest$NestedClass.helloWorldTest=[
2+
Hello World
3+
]

java-snapshot-testing-spock/src/main/groovy/au/com/origin/snapshots/spock/SnapshotMethodInterceptor.groovy

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
package au.com.origin.snapshots.spock
22

33
import au.com.origin.snapshots.Expect
4+
import au.com.origin.snapshots.utils.ReflectionUtils
45
import au.com.origin.snapshots.SnapshotVerifier
56
import au.com.origin.snapshots.logging.LoggingHelper
6-
import lombok.extern.slf4j.Slf4j
77
import org.slf4j.LoggerFactory
88
import org.spockframework.runtime.extension.AbstractMethodInterceptor
9-
import org.spockframework.runtime.extension.IMethodInterceptor
109
import org.spockframework.runtime.extension.IMethodInvocation
1110

1211
import java.lang.reflect.Method
@@ -40,13 +39,16 @@ class SnapshotMethodInterceptor extends AbstractMethodInterceptor {
4039
}
4140

4241
private void updateInstanceVariable(Object testInstance, Method testMethod) {
43-
testInstance.class.declaredFields
44-
.find { it.getType() == Expect.class }
45-
?.with {
46-
Expect expect = Expect.of(snapshotVerifier, testMethod)
47-
it.setAccessible(true)
48-
it.set(testInstance, expect)
49-
}
42+
ReflectionUtils.findFieldByPredicate(testInstance.class, { field -> field.getType() == Expect.class })
43+
.ifPresent({ field ->
44+
Expect expect = Expect.of(snapshotVerifier, testMethod);
45+
ReflectionUtils.makeAccessible(field);
46+
try {
47+
field.set(testInstance, expect);
48+
} catch (IllegalAccessException e) {
49+
throw new RuntimeException(e);
50+
}
51+
});
5052
}
5153

5254
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package au.com.origin.snapshots
2+
3+
import org.junit.runner.RunWith
4+
import org.spockframework.runtime.Sputnik
5+
import spock.lang.Specification
6+
7+
@RunWith(Sputnik.class)
8+
class SpecificationBase extends Specification {
9+
Expect expect;
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package au.com.origin.snapshots
2+
3+
import au.com.origin.snapshots.annotations.SnapshotName
4+
import au.com.origin.snapshots.spock.EnableSnapshots
5+
6+
@EnableSnapshots
7+
class TestBaseSpec extends SpecificationBase {
8+
9+
@SnapshotName("Should use extension")
10+
def "Should use extension"() {
11+
when:
12+
expect.toMatchSnapshot("Hello World")
13+
14+
then:
15+
true
16+
}
17+
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Should use extension=[
2+
Hello World
3+
]

0 commit comments

Comments
 (0)