From ca98f9efd121e2720eb968ccef94e09ec440e7d2 Mon Sep 17 00:00:00 2001 From: RobbyG Date: Sat, 30 Mar 2019 15:03:22 -0700 Subject: [PATCH] Using SnapshotMatcher as a JUnit Extension. Moved common static methods into SnapshotUtils Moving state into ExecutionContext. Extracting config values from Jvm/ExecutionContext Adding unit tests --- pom.xml | 12 +- .../io/github/jsonSnapshot/DefaultConfig.java | 7 - .../java/io/github/jsonSnapshot/Snapshot.java | 11 +- .../github/jsonSnapshot/SnapshotConfig.java | 9 +- .../io/github/jsonSnapshot/SnapshotFile.java | 5 +- .../jsonSnapshot/SnapshotMatchException.java | 4 +- .../github/jsonSnapshot/SnapshotMatcher.java | 92 +----------- .../io/github/jsonSnapshot/SnapshotUtils.java | 78 ++++++++++ .../github/jsonSnapshot/junit5/Constants.java | 17 +++ .../junit5/ExtensionContextRetriever.java | 33 ++++ .../jsonSnapshot/junit5/SnapshotMatcher.java | 142 ++++++++++++++++++ .../junit5/SnapshotMatcherExtension.java | 27 ++++ .../io/github/jsonSnapshot/SnapshotTest.java | 6 +- ...shotMatcherExtensionConfigurationTest.java | 33 ++++ .../junit5/SnapshotMatcherExtensionTest.java | 38 +++++ .../junit5/SnapshotMatcherExtensionTest.snap | 82 ++++++++++ .../junit5/SnapshotMatcherTest.java | 93 ++++++++++++ .../junit5/SnapshotMatcherTest.snap | 0 18 files changed, 579 insertions(+), 110 deletions(-) delete mode 100644 src/main/java/io/github/jsonSnapshot/DefaultConfig.java create mode 100644 src/main/java/io/github/jsonSnapshot/junit5/Constants.java create mode 100644 src/main/java/io/github/jsonSnapshot/junit5/ExtensionContextRetriever.java create mode 100644 src/main/java/io/github/jsonSnapshot/junit5/SnapshotMatcher.java create mode 100644 src/main/java/io/github/jsonSnapshot/junit5/SnapshotMatcherExtension.java create mode 100644 src/test/java/io/github/jsonSnapshot/junit5/SnapshotMatcherExtensionConfigurationTest.java create mode 100644 src/test/java/io/github/jsonSnapshot/junit5/SnapshotMatcherExtensionTest.java create mode 100644 src/test/java/io/github/jsonSnapshot/junit5/SnapshotMatcherExtensionTest.snap create mode 100644 src/test/java/io/github/jsonSnapshot/junit5/SnapshotMatcherTest.java create mode 100644 src/test/java/io/github/jsonSnapshot/junit5/SnapshotMatcherTest.snap diff --git a/pom.xml b/pom.xml index b916558..1f72338 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 io.github.json-snapshot json-snapshot - 1.0.17 + 1.0.18 jar json-snapshot @@ -47,6 +47,7 @@ UTF-8 UTF-8 + 5.4.1 @@ -74,12 +75,17 @@ org.junit.jupiter junit-jupiter-api - 5.3.2 + ${junit.version} org.junit.jupiter junit-jupiter-engine - 5.3.2 + ${junit.version} + + + org.junit.jupiter + junit-jupiter-params + ${junit.version} org.mockito diff --git a/src/main/java/io/github/jsonSnapshot/DefaultConfig.java b/src/main/java/io/github/jsonSnapshot/DefaultConfig.java deleted file mode 100644 index cf71b28..0000000 --- a/src/main/java/io/github/jsonSnapshot/DefaultConfig.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.github.jsonSnapshot; - -import lombok.Getter; - -public class DefaultConfig implements SnapshotConfig { - @Getter private String filePath = "src/test/java/"; -} diff --git a/src/main/java/io/github/jsonSnapshot/Snapshot.java b/src/main/java/io/github/jsonSnapshot/Snapshot.java index 9d73e9a..55045ed 100644 --- a/src/main/java/io/github/jsonSnapshot/Snapshot.java +++ b/src/main/java/io/github/jsonSnapshot/Snapshot.java @@ -1,6 +1,5 @@ package io.github.jsonSnapshot; -import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.Set; @@ -15,22 +14,22 @@ public class Snapshot { private Class clazz; - private Method method; + private String testName; private Function jsonFunction; private Object[] current; - Snapshot( + public Snapshot( SnapshotFile snapshotFile, Class clazz, - Method method, + String method, Function jsonFunction, Object... current) { this.current = current; this.snapshotFile = snapshotFile; this.clazz = clazz; - this.method = method; + this.testName = method; this.jsonFunction = jsonFunction; } @@ -88,6 +87,6 @@ private String takeSnapshot() { } public String getSnapshotName() { - return clazz.getName() + "." + method.getName() + "="; + return String.format("%s.%s=", clazz.getName(), testName); } } diff --git a/src/main/java/io/github/jsonSnapshot/SnapshotConfig.java b/src/main/java/io/github/jsonSnapshot/SnapshotConfig.java index f16c811..48e6834 100644 --- a/src/main/java/io/github/jsonSnapshot/SnapshotConfig.java +++ b/src/main/java/io/github/jsonSnapshot/SnapshotConfig.java @@ -1,5 +1,10 @@ package io.github.jsonSnapshot; -public interface SnapshotConfig { - String getFilePath(); +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class SnapshotConfig { + @Builder.Default private final String filePath = "src/test/java/"; } diff --git a/src/main/java/io/github/jsonSnapshot/SnapshotFile.java b/src/main/java/io/github/jsonSnapshot/SnapshotFile.java index 2303c3f..7b1efa9 100644 --- a/src/main/java/io/github/jsonSnapshot/SnapshotFile.java +++ b/src/main/java/io/github/jsonSnapshot/SnapshotFile.java @@ -1,6 +1,7 @@ package io.github.jsonSnapshot; import java.io.*; +import java.nio.file.Paths; import java.util.Set; import java.util.TreeSet; import java.util.stream.Collectors; @@ -18,9 +19,9 @@ public class SnapshotFile { @Getter private Set rawSnapshots; - SnapshotFile(String filePath, String fileName) throws IOException { + public SnapshotFile(String filePath, String fileName) throws IOException { - this.fileName = filePath + fileName; + this.fileName = Paths.get(filePath, fileName).toString(); StringBuilder fileContent = new StringBuilder(); diff --git a/src/main/java/io/github/jsonSnapshot/SnapshotMatchException.java b/src/main/java/io/github/jsonSnapshot/SnapshotMatchException.java index 9cb2fa4..fc459d5 100644 --- a/src/main/java/io/github/jsonSnapshot/SnapshotMatchException.java +++ b/src/main/java/io/github/jsonSnapshot/SnapshotMatchException.java @@ -2,11 +2,11 @@ public class SnapshotMatchException extends RuntimeException { - SnapshotMatchException(String message) { + public SnapshotMatchException(String message) { super(message); } - SnapshotMatchException(String message, Throwable cause) { + public SnapshotMatchException(String message, Throwable cause) { super(message, cause); } } diff --git a/src/main/java/io/github/jsonSnapshot/SnapshotMatcher.java b/src/main/java/io/github/jsonSnapshot/SnapshotMatcher.java index 57ed01f..4d8456a 100644 --- a/src/main/java/io/github/jsonSnapshot/SnapshotMatcher.java +++ b/src/main/java/io/github/jsonSnapshot/SnapshotMatcher.java @@ -10,44 +10,32 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; -import org.assertj.core.util.Arrays; import org.junit.BeforeClass; import org.junit.Test; import org.junit.jupiter.api.BeforeAll; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.core.PrettyPrinter; -import com.fasterxml.jackson.core.util.DefaultIndenter; -import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; -import com.fasterxml.jackson.core.util.DefaultPrettyPrinter.Indenter; -import com.fasterxml.jackson.core.util.Separators; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; - public class SnapshotMatcher { private static Logger log = LoggerFactory.getLogger(SnapshotMatcher.class); - + private static final SnapshotConfig DEFAULT_CONFIG = SnapshotConfig.builder().build(); private static Class clazz = null; private static SnapshotFile snapshotFile = null; private static List calledSnapshots = new ArrayList<>(); private static Function jsonFunction; public static void start() { - start(new DefaultConfig(), defaultJsonFunction()); + start(DEFAULT_CONFIG, SnapshotUtils.defaultJsonFunction()); } public static void start(SnapshotConfig config) { - start(config, defaultJsonFunction()); + start(config, SnapshotUtils.defaultJsonFunction()); } public static void start(Function jsonFunction) { - start(new DefaultConfig(), jsonFunction); + start(DEFAULT_CONFIG, jsonFunction); } public static void start(SnapshotConfig config, Function jsonFunction) { @@ -94,72 +82,15 @@ public static Snapshot expect(Object firstObject, Object... others) { throw new SnapshotMatchException( "SnapshotTester not yet started! Start it on @BeforeClass/@BeforeAll with SnapshotMatcher.start()"); } - Object[] objects = mergeObjects(firstObject, others); + Object[] objects = SnapshotUtils.mergeObjects(firstObject, others); StackTraceElement stackElement = findStackElement(); Method method = getMethod(clazz, stackElement.getMethodName()); - Snapshot snapshot = new Snapshot(snapshotFile, clazz, method, jsonFunction, objects); - validateExpectCall(snapshot); + Snapshot snapshot = new Snapshot(snapshotFile, clazz, method.getName(), jsonFunction, objects); + SnapshotUtils.validateExpectCall(snapshot, calledSnapshots); calledSnapshots.add(snapshot); return snapshot; } - static Function defaultJsonFunction() { - - ObjectMapper objectMapper = buildObjectMapper(); - - PrettyPrinter pp = buildDefaultPrettyPrinter(); - - return (object) -> { - try { - return objectMapper.writer(pp).writeValueAsString(object); - } catch (Exception e) { - throw new SnapshotMatchException(e.getMessage()); - } - }; - } - - private static PrettyPrinter buildDefaultPrettyPrinter() { - DefaultPrettyPrinter pp = - new DefaultPrettyPrinter("") { - @Override - public DefaultPrettyPrinter withSeparators(Separators separators) { - this._separators = separators; - this._objectFieldValueSeparatorWithSpaces = - separators.getObjectFieldValueSeparator() + " "; - return this; - } - }; - Indenter lfOnlyIndenter = new DefaultIndenter(" ", "\n"); - pp.indentArraysWith(lfOnlyIndenter); - pp.indentObjectsWith(lfOnlyIndenter); - return pp; - } - - private static ObjectMapper buildObjectMapper() { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true); - objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - - objectMapper.setVisibility( - objectMapper - .getSerializationConfig() - .getDefaultVisibilityChecker() - .withFieldVisibility(JsonAutoDetect.Visibility.ANY) - .withGetterVisibility(JsonAutoDetect.Visibility.NONE) - .withSetterVisibility(JsonAutoDetect.Visibility.NONE) - .withCreatorVisibility(JsonAutoDetect.Visibility.NONE)); - return objectMapper; - } - - private static void validateExpectCall(Snapshot snapshot) { - for (Snapshot eachSnapshot : calledSnapshots) { - if (eachSnapshot.getSnapshotName().equals(snapshot.getSnapshotName())) { - throw new SnapshotMatchException( - "You can only call 'expect' once per test method. Try using array of arguments on a single 'expect' call"); - } - } - } - private static Method getMethod(Class clazz, String methodName) { try { return Stream.of(clazz.getDeclaredMethods()) @@ -211,15 +142,6 @@ private static boolean hasTestAnnotation(Method method) { || method.isAnnotationPresent(BeforeAll.class); } - private static Object[] mergeObjects(Object firstObject, Object[] others) { - Object[] objects = new Object[1]; - objects[0] = firstObject; - if (!Arrays.isNullOrEmpty(others)) { - objects = ArrayUtils.addAll(objects, others); - } - return objects; - } - private static Class getClass(String className) { try { return Class.forName(className); diff --git a/src/main/java/io/github/jsonSnapshot/SnapshotUtils.java b/src/main/java/io/github/jsonSnapshot/SnapshotUtils.java index 87def58..dd8c62d 100644 --- a/src/main/java/io/github/jsonSnapshot/SnapshotUtils.java +++ b/src/main/java/io/github/jsonSnapshot/SnapshotUtils.java @@ -8,9 +8,21 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.function.Function; +import org.apache.commons.lang3.ArrayUtils; +import org.assertj.core.util.Arrays; import org.mockito.ArgumentCaptor; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.PrettyPrinter; +import com.fasterxml.jackson.core.util.DefaultIndenter; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.core.util.Separators; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + public class SnapshotUtils { public static HashMap>> extractArgs( @@ -88,4 +100,70 @@ private static HashMap>> process( return result; } + + public static Function defaultJsonFunction() { + + ObjectMapper objectMapper = buildObjectMapper(); + + PrettyPrinter pp = buildDefaultPrettyPrinter(); + + return (object) -> { + try { + return objectMapper.writer(pp).writeValueAsString(object); + } catch (Exception e) { + throw new SnapshotMatchException(e.getMessage()); + } + }; + } + + public static PrettyPrinter buildDefaultPrettyPrinter() { + DefaultPrettyPrinter pp = + new DefaultPrettyPrinter("") { + @Override + public DefaultPrettyPrinter withSeparators(Separators separators) { + this._separators = separators; + this._objectFieldValueSeparatorWithSpaces = + separators.getObjectFieldValueSeparator() + " "; + return this; + } + }; + DefaultPrettyPrinter.Indenter lfOnlyIndenter = new DefaultIndenter(" ", "\n"); + pp.indentArraysWith(lfOnlyIndenter); + pp.indentObjectsWith(lfOnlyIndenter); + return pp; + } + + private static ObjectMapper buildObjectMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true); + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + + objectMapper.setVisibility( + objectMapper + .getSerializationConfig() + .getDefaultVisibilityChecker() + .withFieldVisibility(JsonAutoDetect.Visibility.ANY) + .withGetterVisibility(JsonAutoDetect.Visibility.NONE) + .withSetterVisibility(JsonAutoDetect.Visibility.NONE) + .withCreatorVisibility(JsonAutoDetect.Visibility.NONE)); + return objectMapper; + } + + public static void validateExpectCall(Snapshot snapshot, List calledSnapshots) { + for (Snapshot eachSnapshot : calledSnapshots) { + if (eachSnapshot.getSnapshotName().equals(snapshot.getSnapshotName())) { + throw new SnapshotMatchException( + "You can only call 'expect' once per test method. Try using array of arguments on a single 'expect' call"); + } + } + } + + public static Object[] mergeObjects(Object firstObject, Object[] others) { + Object[] objects = new Object[1]; + objects[0] = firstObject; + if (!Arrays.isNullOrEmpty(others)) { + objects = ArrayUtils.addAll(objects, others); + } + return objects; + } } diff --git a/src/main/java/io/github/jsonSnapshot/junit5/Constants.java b/src/main/java/io/github/jsonSnapshot/junit5/Constants.java new file mode 100644 index 0000000..6fa3379 --- /dev/null +++ b/src/main/java/io/github/jsonSnapshot/junit5/Constants.java @@ -0,0 +1,17 @@ +package io.github.jsonSnapshot.junit5; + +import org.junit.jupiter.api.extension.ExtensionContext; + +public class Constants { + + public static final ExtensionContext.Namespace NAMESPACE = + ExtensionContext.Namespace.create(SnapshotMatcher.class); + public static final String SNAPSHOT_FILE = "snapshotFile"; + public static final String CALLED_SNAPSHOTS = "calledSnapshots"; + + // ***************** JVM Config Values ************************* + public static final String SNAPSHOT_ROOT_DIR = + "junit.jupiter.extensions.snapshotmatcher.snaps.rootDir"; + + private Constants() {} +} diff --git a/src/main/java/io/github/jsonSnapshot/junit5/ExtensionContextRetriever.java b/src/main/java/io/github/jsonSnapshot/junit5/ExtensionContextRetriever.java new file mode 100644 index 0000000..fd2318a --- /dev/null +++ b/src/main/java/io/github/jsonSnapshot/junit5/ExtensionContextRetriever.java @@ -0,0 +1,33 @@ +package io.github.jsonSnapshot.junit5; + +import java.util.Optional; + +import org.junit.jupiter.api.extension.ExtensionContext; + +public class ExtensionContextRetriever { + + private static ExtensionContextRetriever sInstance; + private ThreadLocal extensionContextThreadLocal = new ThreadLocal<>(); + + private ExtensionContextRetriever() {} + + public static ExtensionContextRetriever getInstance() { + if (sInstance == null) { + sInstance = new ExtensionContextRetriever(); + } + + return sInstance; + } + + public void set(ExtensionContext context) { + extensionContextThreadLocal.set(context); + } + + public Optional get() { + return Optional.ofNullable(extensionContextThreadLocal.get()); + } + + public void remove() { + extensionContextThreadLocal.remove(); + } +} diff --git a/src/main/java/io/github/jsonSnapshot/junit5/SnapshotMatcher.java b/src/main/java/io/github/jsonSnapshot/junit5/SnapshotMatcher.java new file mode 100644 index 0000000..0b85bdf --- /dev/null +++ b/src/main/java/io/github/jsonSnapshot/junit5/SnapshotMatcher.java @@ -0,0 +1,142 @@ +package io.github.jsonSnapshot.junit5; + +import static io.github.jsonSnapshot.SnapshotUtils.defaultJsonFunction; +import static io.github.jsonSnapshot.junit5.Constants.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.github.jsonSnapshot.Snapshot; +import io.github.jsonSnapshot.SnapshotConfig; +import io.github.jsonSnapshot.SnapshotFile; +import io.github.jsonSnapshot.SnapshotMatchException; +import io.github.jsonSnapshot.SnapshotUtils; + +public class SnapshotMatcher { + + private static Logger log = LoggerFactory.getLogger(SnapshotMatcher.class); + private static final SnapshotConfig DEFAULT_CONFIG = SnapshotConfig.builder().build(); + private static final String MISSING_CONTEXT_ERR = + "SnapshotTester not yet started! Ensure that you have placed @ExtendWith(SnapshotMatcherExtension.class) on your test class"; + private static Function jsonFunction; + + public static void start() { + start(DEFAULT_CONFIG, defaultJsonFunction()); + } + + public static void start(SnapshotConfig config) { + start(config, defaultJsonFunction()); + } + + public static void start(SnapshotConfig config, Function jsonFunction) { + SnapshotMatcher.jsonFunction = jsonFunction; + + try { + ExtensionContext context = + ExtensionContextRetriever.getInstance() + .get() + .orElseThrow(() -> new SnapshotMatchException(MISSING_CONTEXT_ERR)); + Class clazz = + context + .getTestClass() + .orElseThrow( + () -> new SnapshotMatchException("JUnit is unable to locate a class to test")); + context + .getStore(NAMESPACE) + .put( + SNAPSHOT_FILE, + new SnapshotFile( + config.getFilePath(), clazz.getName().replaceAll("\\.", "/") + ".snap")); + context.getStore(NAMESPACE).put(CALLED_SNAPSHOTS, new ArrayList()); + } catch (IOException e) { + throw new SnapshotMatchException(e.getMessage()); + } + } + + public static void validateSnapshots() { + ExtensionContext context = + ExtensionContextRetriever.getInstance() + .get() + .orElseThrow(() -> new SnapshotMatchException(MISSING_CONTEXT_ERR)); + List calledSnapshots = context.getStore(NAMESPACE).get(CALLED_SNAPSHOTS, List.class); + SnapshotFile snapshotFile = context.getStore(NAMESPACE).get(SNAPSHOT_FILE, SnapshotFile.class); + + Set rawSnapshots = snapshotFile.getRawSnapshots(); + List snapshotNames = + calledSnapshots.stream().map(Snapshot::getSnapshotName).collect(Collectors.toList()); + List unusedRawSnapshots = new ArrayList<>(); + + for (String rawSnapshot : rawSnapshots) { + boolean foundSnapshot = false; + for (String snapshotName : snapshotNames) { + if (rawSnapshot.contains(snapshotName)) { + foundSnapshot = true; + } + } + if (!foundSnapshot) { + unusedRawSnapshots.add(rawSnapshot); + } + } + if (unusedRawSnapshots.size() > 0) { + log.warn( + "All unused Snapshots: " + + StringUtils.join(unusedRawSnapshots, "\n") + + ". Consider deleting the snapshot file to recreate it!"); + } + } + + public static Snapshot expect(Object firstObject, Object... others) { + ExtensionContext context = + ExtensionContextRetriever.getInstance() + .get() + .orElseThrow(() -> new SnapshotMatchException(MISSING_CONTEXT_ERR)); + + Object[] objects = SnapshotUtils.mergeObjects(firstObject, others); + List calledSnapshots = context.getStore(NAMESPACE).get(CALLED_SNAPSHOTS, List.class); + SnapshotFile snapshotFile = context.getStore(NAMESPACE).get(SNAPSHOT_FILE, SnapshotFile.class); + Class clazz = + context + .getTestClass() + .orElseThrow( + () -> + new SnapshotMatchException("Junit is unable to locate a class/method to test")); + + /** + * The DISPLAY NAME for a parameterized test is built dynamically by Junit. We further namespace + * these dynamic names with the name of the method being tested to prevent the small chance of + * naming collisions with other parameterized tests. + */ + String testName = + context + .getParent() + .map( + (parentContext) -> { + if (parentContext.getTestMethod().isPresent() + && context.getTestMethod().isPresent()) { + if (parentContext.getTestMethod().get().equals(context.getTestMethod().get())) { + return String.format( + "%s.%s", + context.getTestMethod().get().getName(), context.getDisplayName()); + } + } + return context.getDisplayName(); + }) + .orElseGet(() -> context.getDisplayName()); + + // TODO kebab case the testName + + Snapshot snapshot = new Snapshot(snapshotFile, clazz, testName, jsonFunction, objects); + SnapshotUtils.validateExpectCall(snapshot, calledSnapshots); + calledSnapshots.add(snapshot); + return snapshot; + } +} diff --git a/src/main/java/io/github/jsonSnapshot/junit5/SnapshotMatcherExtension.java b/src/main/java/io/github/jsonSnapshot/junit5/SnapshotMatcherExtension.java new file mode 100644 index 0000000..2f76865 --- /dev/null +++ b/src/main/java/io/github/jsonSnapshot/junit5/SnapshotMatcherExtension.java @@ -0,0 +1,27 @@ +package io.github.jsonSnapshot.junit5; + +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +import io.github.jsonSnapshot.SnapshotConfig; + +public class SnapshotMatcherExtension implements BeforeEachCallback, AfterEachCallback { + + @Override + public void beforeEach(ExtensionContext extensionContext) throws Exception { + ExtensionContextRetriever.getInstance().set(extensionContext); + SnapshotConfig.SnapshotConfigBuilder config = SnapshotConfig.builder(); + + extensionContext + .getConfigurationParameter(Constants.SNAPSHOT_ROOT_DIR) + .ifPresent((path) -> config.filePath(path)); + SnapshotMatcher.start(config.build()); + } + + @Override + public void afterEach(ExtensionContext extensionContext) throws Exception { + SnapshotMatcher.validateSnapshots(); + ExtensionContextRetriever.getInstance().remove(); + } +} diff --git a/src/test/java/io/github/jsonSnapshot/SnapshotTest.java b/src/test/java/io/github/jsonSnapshot/SnapshotTest.java index 4dc3e26..325599d 100644 --- a/src/test/java/io/github/jsonSnapshot/SnapshotTest.java +++ b/src/test/java/io/github/jsonSnapshot/SnapshotTest.java @@ -19,7 +19,7 @@ @ExtendWith(MockitoExtension.class) class SnapshotTest { - private static final SnapshotConfig DEFAULT_CONFIG = new DefaultConfig(); + private static final SnapshotConfig DEFAULT_CONFIG = SnapshotConfig.builder().build(); private static final String FILE_PATH = "src/test/java/anyFilePath"; private static final String SNAPSHOT_NAME = "java.lang.String.toString="; private static final String SNAPSHOT = "java.lang.String.toString=[\n \"anyObject\"\n]"; @@ -35,8 +35,8 @@ void setUp() throws NoSuchMethodException, IOException { new Snapshot( snapshotFile, String.class, - String.class.getDeclaredMethod("toString"), - SnapshotMatcher.defaultJsonFunction(), + "toString", + SnapshotUtils.defaultJsonFunction(), "anyObject"); } diff --git a/src/test/java/io/github/jsonSnapshot/junit5/SnapshotMatcherExtensionConfigurationTest.java b/src/test/java/io/github/jsonSnapshot/junit5/SnapshotMatcherExtensionConfigurationTest.java new file mode 100644 index 0000000..f3a3dd6 --- /dev/null +++ b/src/test/java/io/github/jsonSnapshot/junit5/SnapshotMatcherExtensionConfigurationTest.java @@ -0,0 +1,33 @@ +package io.github.jsonSnapshot.junit5; + +import static io.github.jsonSnapshot.junit5.SnapshotMatcher.expect; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; + +public class SnapshotMatcherExtensionConfigurationTest { + @TempDir public static Path tempDir; + + @BeforeAll + public static void beforeAll() { + System.setProperty(Constants.SNAPSHOT_ROOT_DIR, tempDir.toString()); + } + + @Test + @ExtendWith(SnapshotMatcherExtension.class) + public void testSnapshotRootDirectoryIsUpdatedByJvmConfig() { + expect("hello world").toMatchSnapshot(); + + String[] files = + Paths.get(tempDir.toString(), "io", "github", "jsonSnapshot", "junit5").toFile().list(); + + assertEquals(1, files.length); + assertEquals("SnapshotMatcherExtensionConfigurationTest.snap", files[0]); + } +} diff --git a/src/test/java/io/github/jsonSnapshot/junit5/SnapshotMatcherExtensionTest.java b/src/test/java/io/github/jsonSnapshot/junit5/SnapshotMatcherExtensionTest.java new file mode 100644 index 0000000..0169983 --- /dev/null +++ b/src/test/java/io/github/jsonSnapshot/junit5/SnapshotMatcherExtensionTest.java @@ -0,0 +1,38 @@ +package io.github.jsonSnapshot.junit5; + +import static io.github.jsonSnapshot.junit5.SnapshotMatcher.expect; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import io.github.jsonSnapshot.SnapshotMatchException; + +public class SnapshotMatcherExtensionTest { + + @DisplayName("Test update events are processed") + @ParameterizedTest(name = "test{0}") + @ValueSource(strings = {"one", "two", "three", "four", "five", "six", "seven"}) + @ExtendWith(SnapshotMatcherExtension.class) + public void testParameterizedTest(String param) { + expect(param, param.toUpperCase()).toMatchSnapshot(); + } + + @DisplayName("Test update events are processed2") + @ParameterizedTest(name = "test{0}") + @ValueSource(strings = {"one", "two", "three", "four", "five", "six", "seven"}) + @ExtendWith(SnapshotMatcherExtension.class) + public void testParameterizedTest2(String param) { + expect(param, param.toUpperCase()).toMatchSnapshot(); + } + + @Test + public void testCallsToSnapshotMatcherFailWhenExtensionIsNotPresent() { + assertThrows(SnapshotMatchException.class, () -> expect("uh oh").toMatchSnapshot()); + assertThrows(SnapshotMatchException.class, () -> SnapshotMatcher.start()); + assertThrows(SnapshotMatchException.class, () -> SnapshotMatcher.validateSnapshots()); + } +} diff --git a/src/test/java/io/github/jsonSnapshot/junit5/SnapshotMatcherExtensionTest.snap b/src/test/java/io/github/jsonSnapshot/junit5/SnapshotMatcherExtensionTest.snap new file mode 100644 index 0000000..ca87dd7 --- /dev/null +++ b/src/test/java/io/github/jsonSnapshot/junit5/SnapshotMatcherExtensionTest.snap @@ -0,0 +1,82 @@ +io.github.jsonSnapshot.junit5.SnapshotMatcherExtensionTest.testParameterizedTest.testfive=[ + "five", + "FIVE" +] + + +io.github.jsonSnapshot.junit5.SnapshotMatcherExtensionTest.testParameterizedTest.testfour=[ + "four", + "FOUR" +] + + +io.github.jsonSnapshot.junit5.SnapshotMatcherExtensionTest.testParameterizedTest.testone=[ + "one", + "ONE" +] + + +io.github.jsonSnapshot.junit5.SnapshotMatcherExtensionTest.testParameterizedTest.testseven=[ + "seven", + "SEVEN" +] + + +io.github.jsonSnapshot.junit5.SnapshotMatcherExtensionTest.testParameterizedTest.testsix=[ + "six", + "SIX" +] + + +io.github.jsonSnapshot.junit5.SnapshotMatcherExtensionTest.testParameterizedTest.testthree=[ + "three", + "THREE" +] + + +io.github.jsonSnapshot.junit5.SnapshotMatcherExtensionTest.testParameterizedTest.testtwo=[ + "two", + "TWO" +] + + +io.github.jsonSnapshot.junit5.SnapshotMatcherExtensionTest.testParameterizedTest2.testfive=[ + "five", + "FIVE" +] + + +io.github.jsonSnapshot.junit5.SnapshotMatcherExtensionTest.testParameterizedTest2.testfour=[ + "four", + "FOUR" +] + + +io.github.jsonSnapshot.junit5.SnapshotMatcherExtensionTest.testParameterizedTest2.testone=[ + "one", + "ONE" +] + + +io.github.jsonSnapshot.junit5.SnapshotMatcherExtensionTest.testParameterizedTest2.testseven=[ + "seven", + "SEVEN" +] + + +io.github.jsonSnapshot.junit5.SnapshotMatcherExtensionTest.testParameterizedTest2.testsix=[ + "six", + "SIX" +] + + +io.github.jsonSnapshot.junit5.SnapshotMatcherExtensionTest.testParameterizedTest2.testthree=[ + "three", + "THREE" +] + + +io.github.jsonSnapshot.junit5.SnapshotMatcherExtensionTest.testParameterizedTest2.testtwo=[ + "two", + "TWO" +] \ No newline at end of file diff --git a/src/test/java/io/github/jsonSnapshot/junit5/SnapshotMatcherTest.java b/src/test/java/io/github/jsonSnapshot/junit5/SnapshotMatcherTest.java new file mode 100644 index 0000000..0b8cfbb --- /dev/null +++ b/src/test/java/io/github/jsonSnapshot/junit5/SnapshotMatcherTest.java @@ -0,0 +1,93 @@ +package io.github.jsonSnapshot.junit5; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.Callable; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import io.github.jsonSnapshot.Snapshot; +import io.github.jsonSnapshot.SnapshotConfig; +import io.github.jsonSnapshot.SnapshotFile; +import io.github.jsonSnapshot.SnapshotUtils; + +public class SnapshotMatcherTest { + + @Mock private ExtensionContext extensionContext; + @Mock private ExtensionContext.Store store; + + @BeforeEach + public void beforeEach() { + MockitoAnnotations.initMocks(this); + ExtensionContextRetriever.getInstance().set(extensionContext); + } + + @Test + public void testSnapshotMatcherCanBeInitialized() throws Exception { + when(extensionContext.getStore(any(ExtensionContext.Namespace.class))).thenReturn(store); + when(extensionContext.getTestClass()).thenReturn(Optional.of(SnapshotMatcherTest.class)); + + verifyInitialization( + () -> { + SnapshotMatcher.start(); + return null; + }); + } + + @Test + public void testSnapshotMatcherCanBeInitializedWithSnapshotConfig() throws Exception { + when(extensionContext.getStore(any(ExtensionContext.Namespace.class))).thenReturn(store); + when(extensionContext.getTestClass()).thenReturn(Optional.of(SnapshotMatcherTest.class)); + + verifyInitialization( + () -> { + SnapshotMatcher.start(SnapshotConfig.builder().build()); + return null; + }); + } + + @Test + public void testSnapshotMatcherCanBeInitializedWithSnapshotConfigAndJsonBuilder() + throws Exception { + when(extensionContext.getStore(any(ExtensionContext.Namespace.class))).thenReturn(store); + when(extensionContext.getTestClass()).thenReturn(Optional.of(SnapshotMatcherTest.class)); + + verifyInitialization( + () -> { + SnapshotMatcher.start( + SnapshotConfig.builder().build(), SnapshotUtils.defaultJsonFunction()); + return null; + }); + } + + @Test + public void testSnapshotIsCreated() { + List snapshots = new ArrayList<>(); + when(extensionContext.getStore(any(ExtensionContext.Namespace.class))).thenReturn(store); + when(extensionContext.getDisplayName()).thenReturn("testSnapshotIsCreated"); + when(extensionContext.getTestClass()).thenReturn(Optional.of(SnapshotMatcherTest.class)); + when(store.get(Constants.CALLED_SNAPSHOTS, List.class)).thenReturn(snapshots); + + Snapshot snapshot = SnapshotMatcher.expect("hello world"); + assertEquals(1, snapshots.size()); + assertTrue(snapshot.getSnapshotName().endsWith("testSnapshotIsCreated=")); + } + + private void verifyInitialization(Callable initializer) throws Exception { + + initializer.call(); + verify(extensionContext).getTestClass(); + verify(extensionContext, times(2)).getStore(Constants.NAMESPACE); + verify(store).put(eq(Constants.SNAPSHOT_FILE), any(SnapshotFile.class)); + verify(store).put(eq(Constants.CALLED_SNAPSHOTS), any(List.class)); + } +} diff --git a/src/test/java/io/github/jsonSnapshot/junit5/SnapshotMatcherTest.snap b/src/test/java/io/github/jsonSnapshot/junit5/SnapshotMatcherTest.snap new file mode 100644 index 0000000..e69de29