Skip to content

Commit 194f256

Browse files
authored
#patch: #145 make updating snapshots simpler (#146)
- Allow snapshots to be updated by toggling `update-snapshot` property in `snapshot.properties`. - Deprecate the passing of system property `-PupdateSnapshot`. - Add some API deprecation logs for V5
1 parent 320d11f commit 194f256

File tree

16 files changed

+259
-95
lines changed

16 files changed

+259
-95
lines changed

README.md

+14-41
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ reporters=au.com.origin.snapshots.reporters.PlainTextSnapshotReporter
5656
snapshot-dir=__snapshots__
5757
output-dir=src/test/java
5858
ci-env-var=CI
59+
update-snapshot=none
5960
```
6061

6162
3. Enable snapshot testing and write your first test
@@ -447,17 +448,18 @@ Often your IDE has an excellent file comparison tool.
447448

448449
This file allows you to conveniently setup global defaults
449450

450-
| key | Description |
451-
|------------------|----------------------------------------------------------------------------------------------------------------------------------------|
452-
|serializer | Class name of the [serializer](#supplying-a-custom-snapshotserializer), default serializer |
453-
|serializer.{name} | Class name of the [serializer](#supplying-a-custom-snapshotserializer), accessible via `.serializer("{name}")` |
454-
|comparator | Class name of the [comparator](#supplying-a-custom-snapshotcomparator) |
455-
|comparator.{name} | Class name of the [comparator](#supplying-a-custom-snapshotcomparator), accessible via `.comparator("{name}")` |
456-
|reporters | Comma separated list of class names to use as [reporters](#supplying-a-custom-snapshotreporter) |
457-
|reporters.{name} | Comma separated list of class names to use as [reporters](#supplying-a-custom-snapshotreporter), accessible via `.reporters("{name}")` |
458-
|snapshot-dir | Name of sub-folder holding your snapshots |
459-
|output-dir | Base directory of your test files (although it can be a different directory if you want) |
460-
|ci-env-var | Name of environment variable used to detect if we are running on a Build Server |
451+
| key | Description |
452+
|------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
453+
|serializer | Class name of the [serializer](#supplying-a-custom-snapshotserializer), default serializer |
454+
|serializer.{name} | Class name of the [serializer](#supplying-a-custom-snapshotserializer), accessible via `.serializer("{name}")` |
455+
|comparator | Class name of the [comparator](#supplying-a-custom-snapshotcomparator) |
456+
|comparator.{name} | Class name of the [comparator](#supplying-a-custom-snapshotcomparator), accessible via `.comparator("{name}")` |
457+
|reporters | Comma separated list of class names to use as [reporters](#supplying-a-custom-snapshotreporter) |
458+
|reporters.{name} | Comma separated list of class names to use as [reporters](#supplying-a-custom-snapshotreporter), accessible via `.reporters("{name}")` |
459+
|snapshot-dir | Name of sub-folder holding your snapshots |
460+
|output-dir | Base directory of your test files (although it can be a different directory if you want) |
461+
|ci-env-var | Name of environment variable used to detect if we are running on a Build Server |
462+
|update-snapshot | Similar to `--updateSnapshot` in [Jest](https://jestjs.io/docs/en/snapshot-testing#updating-snapshots) <br/>[all]=update all snapsohts<br/>[none]=update no snapshots<br/>[MyTest1,MyTest2]=update snapshots in these classes only<br/><br/>*Note: must be set to [none] on CI |
461463

462464
For example:
463465

@@ -471,6 +473,7 @@ reporters=au.com.origin.snapshots.reporters.PlainTextSnapshotReporter
471473
snapshot-dir=__snapshots__
472474
output-dir=src/test/java
473475
ci-env-var=CI
476+
update-snapshot=none
474477
```
475478

476479
## Parameterized tests
@@ -784,36 +787,6 @@ public class JUnit5ResolutionHierarchyExample {
784787
}
785788
```
786789

787-
## Automatically overwriting snapshots via `-PupdateSnapshot=filter`
788-
789-
Often - after analysing each snapshot and verifying it is correct, you will need to override the existing
790-
snapshots.
791-
792-
Note that you may need to do some Gradle trickery to make this visible to your actual tests
793-
794-
```groovy
795-
test {
796-
systemProperty "updateSnapshot", project.getProperty("updateSnapshot")
797-
}
798-
```
799-
800-
Instead of deleting or manually modifying each snapshot you can pass `-PupdateSnapshot` which is equivalent to
801-
the `--updateSnapshot` flag in [Jest](https://jestjs.io/docs/en/snapshot-testing#updating-snapshots)
802-
803-
#### Update all snapshots automatically
804-
805-
```
806-
-PupdateSnapshot
807-
```
808-
809-
#### Update selected snapshots only using `filter`
810-
811-
pass the class names you want to update to `filter`
812-
813-
```
814-
-PupdateSnapshot=UserService,PermissionRepository
815-
```
816-
817790
# Troubleshooting
818791

819792
**I'm seeing this error in my logs**

java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/SnapshotContext.java

+12-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import au.com.origin.snapshots.comparators.SnapshotComparator;
55
import au.com.origin.snapshots.config.SnapshotConfig;
66
import au.com.origin.snapshots.exceptions.ReservedWordException;
7+
import au.com.origin.snapshots.exceptions.SnapshotExtensionException;
78
import au.com.origin.snapshots.exceptions.SnapshotMatchException;
89
import au.com.origin.snapshots.reporters.SnapshotReporter;
910
import au.com.origin.snapshots.serializers.SnapshotSerializer;
@@ -118,6 +119,12 @@ public void toMatchSnapshot() {
118119
}
119120

120121
private boolean shouldUpdateSnapshot() {
122+
if (snapshotConfig.updateSnapshot().isPresent() && snapshotConfig.isCI()) {
123+
throw new SnapshotExtensionException(
124+
"isCI=true & update-snapshot="
125+
+ snapshotConfig.updateSnapshot()
126+
+ ". Updating snapshots on CI is not allowed");
127+
}
121128
if (snapshotConfig.updateSnapshot().isPresent()) {
122129
return resolveSnapshotIdentifier().contains(snapshotConfig.updateSnapshot().get());
123130
} else {
@@ -126,9 +133,11 @@ private boolean shouldUpdateSnapshot() {
126133
}
127134

128135
private Snapshot getRawSnapshot(Collection<Snapshot> rawSnapshots) {
129-
for (Snapshot rawSnapshot : rawSnapshots) {
130-
if (rawSnapshot.getIdentifier().equals(resolveSnapshotIdentifier())) {
131-
return rawSnapshot;
136+
synchronized (rawSnapshots) {
137+
for (Snapshot rawSnapshot : rawSnapshots) {
138+
if (rawSnapshot.getIdentifier().equals(resolveSnapshotIdentifier())) {
139+
return rawSnapshot;
140+
}
132141
}
133142
}
134143
return null;

java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/SnapshotFile.java

+7-5
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,13 @@ public synchronized File createFileIfNotExists(String filename) {
108108
return path.toFile();
109109
}
110110

111-
public synchronized void pushSnapshot(Snapshot snapshot) {
112-
snapshots.add(snapshot);
113-
TreeSet<String> rawSnapshots =
114-
snapshots.stream().map(Snapshot::raw).collect(Collectors.toCollection(TreeSet::new));
115-
updateFile(this.fileName, rawSnapshots);
111+
public void pushSnapshot(Snapshot snapshot) {
112+
synchronized (snapshots) {
113+
snapshots.add(snapshot);
114+
TreeSet<String> rawSnapshots =
115+
snapshots.stream().map(Snapshot::raw).collect(Collectors.toCollection(TreeSet::new));
116+
updateFile(this.fileName, rawSnapshots);
117+
}
116118
}
117119

118120
public synchronized void pushDebugSnapshot(Snapshot snapshot) {

java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/config/PropertyResolvingSnapshotConfig.java

+33
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22

33
import au.com.origin.snapshots.SnapshotProperties;
44
import au.com.origin.snapshots.comparators.SnapshotComparator;
5+
import au.com.origin.snapshots.exceptions.MissingSnapshotPropertiesKeyException;
6+
import au.com.origin.snapshots.logging.LoggingHelper;
57
import au.com.origin.snapshots.reporters.SnapshotReporter;
68
import au.com.origin.snapshots.serializers.SnapshotSerializer;
79
import java.util.List;
10+
import java.util.Optional;
11+
import lombok.extern.slf4j.Slf4j;
812

13+
@Slf4j
914
public class PropertyResolvingSnapshotConfig implements SnapshotConfig {
1015

1116
@Override
@@ -18,6 +23,34 @@ public String getSnapshotDir() {
1823
return SnapshotProperties.getOrThrow("snapshot-dir");
1924
}
2025

26+
@Override
27+
public Optional<String> updateSnapshot() {
28+
// This was the original way to update snapshots
29+
Optional<String> legacyFlag =
30+
Optional.ofNullable(System.getProperty(JVM_UPDATE_SNAPSHOTS_PARAMETER));
31+
if (legacyFlag.isPresent()) {
32+
LoggingHelper.deprecatedV5(
33+
log,
34+
"Passing -PupdateSnapshot will be removed in a future release. Consider using snapshot.properties 'update-snapshot' toggle instead");
35+
return legacyFlag;
36+
}
37+
38+
try {
39+
String updateSnapshot = SnapshotProperties.getOrThrow("update-snapshot");
40+
if ("all".equals(updateSnapshot)) {
41+
return Optional.of("");
42+
} else if ("none".equals(updateSnapshot)) {
43+
return Optional.empty();
44+
}
45+
return Optional.of(updateSnapshot);
46+
} catch (MissingSnapshotPropertiesKeyException ex) {
47+
LoggingHelper.deprecatedV5(
48+
log,
49+
"You do not have 'update-snapshot=none' defined in your snapshot.properties - consider adding it now");
50+
return Optional.empty();
51+
}
52+
}
53+
2154
@Override
2255
public SnapshotSerializer getSerializer() {
2356
return SnapshotProperties.getInstance("serializer");

java-snapshot-testing-core/src/main/java/au/com/origin/snapshots/config/SnapshotConfig.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* library
1414
*/
1515
public interface SnapshotConfig {
16-
String JVM_UPDATE_SNAPSHOTS_PARAMETER = "updateSnapshot";
16+
@Deprecated String JVM_UPDATE_SNAPSHOTS_PARAMETER = "updateSnapshot";
1717

1818
/**
1919
* The base directory where files get written (excluding package directories) default:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package au.com.origin.snapshots.logging;
2+
3+
import org.slf4j.Logger;
4+
5+
public class LoggingHelper {
6+
7+
public static void deprecatedV5(Logger log, String message) {
8+
log.warn(
9+
"Deprecation Warning:\n " + message + "\n\nThis feature will be removed in version 5.X");
10+
}
11+
}

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

+1-7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.junit.jupiter.api.extension.ExtendWith;
1717
import org.mockito.junit.jupiter.MockitoExtension;
1818

19+
@Deprecated
1920
@ExtendWith(MockitoExtension.class)
2021
public class UpdateSnapshotPropertyTest {
2122

@@ -72,13 +73,6 @@ void shouldUpdateSnapshot(TestInfo testInfo) throws IOException {
7273
+ "]");
7374
}
7475

75-
@Disabled
76-
@Test
77-
void shouldUpdateAllSnapshots() throws IOException {
78-
System.setProperty(SnapshotConfig.JVM_UPDATE_SNAPSHOTS_PARAMETER, "");
79-
// FIXME
80-
}
81-
8276
@Test
8377
void shouldNotUpdateSnapshot(TestInfo testInfo) {
8478
SnapshotVerifier snapshotVerifier =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package au.com.origin.snapshots;
2+
3+
import static org.junit.jupiter.api.Assertions.assertThrows;
4+
5+
import au.com.origin.snapshots.config.BaseSnapshotConfig;
6+
import au.com.origin.snapshots.config.SnapshotConfig;
7+
import au.com.origin.snapshots.exceptions.SnapshotMatchException;
8+
import java.io.File;
9+
import java.io.IOException;
10+
import java.nio.charset.StandardCharsets;
11+
import java.nio.file.Files;
12+
import java.nio.file.Path;
13+
import java.nio.file.Paths;
14+
import java.util.Optional;
15+
import org.assertj.core.api.Assertions;
16+
import org.junit.jupiter.api.BeforeEach;
17+
import org.junit.jupiter.api.Test;
18+
import org.junit.jupiter.api.TestInfo;
19+
import org.junit.jupiter.api.extension.ExtendWith;
20+
import org.mockito.junit.jupiter.MockitoExtension;
21+
22+
@ExtendWith(MockitoExtension.class)
23+
public class UpdateSnapshotTest {
24+
25+
@BeforeEach
26+
public void beforeEach() throws Exception {
27+
File file =
28+
new File("src/test/java/au/com/origin/snapshots/__snapshots__/UpdateSnapshotTest.snap");
29+
String content =
30+
"au.com.origin.snapshots.UpdateSnapshotTest.canUpdateAllSnapshots=[\n"
31+
+ "OLD\n"
32+
+ "]\n"
33+
+ "\n"
34+
+ "\n"
35+
+ "au.com.origin.snapshots.UpdateSnapshotTest.canUpdateClassNameSnapshots=[\n"
36+
+ "OLD\n"
37+
+ "]\n"
38+
+ "\n"
39+
+ "\n"
40+
+ "au.com.origin.snapshots.UpdateSnapshotTest.canUpdateNoSnapshots=[\n"
41+
+ "OLD\n"
42+
+ "]";
43+
Path parentDir = file.getParentFile().toPath();
44+
if (!Files.exists(parentDir)) {
45+
Files.createDirectories(parentDir);
46+
}
47+
Files.write(file.toPath(), content.getBytes(StandardCharsets.UTF_8));
48+
}
49+
50+
@Test
51+
void canUpdateAllSnapshots(TestInfo testInfo) throws IOException {
52+
SnapshotConfig config =
53+
new BaseSnapshotConfig() {
54+
@Override
55+
public Optional<String> updateSnapshot() {
56+
return Optional.of("");
57+
}
58+
};
59+
SnapshotVerifier snapshotVerifier =
60+
new SnapshotVerifier(config, testInfo.getTestClass().get(), false);
61+
Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get());
62+
expect.toMatchSnapshot("NEW");
63+
snapshotVerifier.validateSnapshots();
64+
65+
String content =
66+
new String(
67+
Files.readAllBytes(
68+
Paths.get(
69+
"src/test/java/au/com/origin/snapshots/__snapshots__/UpdateSnapshotTest.snap")),
70+
StandardCharsets.UTF_8);
71+
Assertions.assertThat(content)
72+
.isEqualTo(
73+
"au.com.origin.snapshots.UpdateSnapshotTest.canUpdateAllSnapshots=[\n"
74+
+ "NEW\n"
75+
+ "]\n"
76+
+ "\n"
77+
+ "\n"
78+
+ "au.com.origin.snapshots.UpdateSnapshotTest.canUpdateClassNameSnapshots=[\n"
79+
+ "OLD\n"
80+
+ "]\n"
81+
+ "\n"
82+
+ "\n"
83+
+ "au.com.origin.snapshots.UpdateSnapshotTest.canUpdateNoSnapshots=[\n"
84+
+ "OLD\n"
85+
+ "]");
86+
}
87+
88+
@Test
89+
void canUpdateNoSnapshots(TestInfo testInfo) {
90+
SnapshotConfig config =
91+
new BaseSnapshotConfig() {
92+
@Override
93+
public Optional<String> updateSnapshot() {
94+
return Optional.empty();
95+
}
96+
};
97+
SnapshotVerifier snapshotVerifier =
98+
new SnapshotVerifier(config, testInfo.getTestClass().get(), false);
99+
Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get());
100+
assertThrows(SnapshotMatchException.class, () -> expect.toMatchSnapshot("FOOBAR"));
101+
}
102+
103+
@Test
104+
public void canUpdateNewSnapshots() {
105+
SnapshotConfig config =
106+
new BaseSnapshotConfig() {
107+
@Override
108+
public Optional<String> updateSnapshot() {
109+
return Optional.of("new");
110+
}
111+
};
112+
113+
// TODO Pending Implementation
114+
}
115+
116+
@Test
117+
public void canUpdateClassNameSnapshots(TestInfo testInfo) throws IOException {
118+
SnapshotConfig config =
119+
new BaseSnapshotConfig() {
120+
@Override
121+
public Optional<String> updateSnapshot() {
122+
return Optional.of("UpdateSnapshotTest");
123+
}
124+
};
125+
SnapshotVerifier snapshotVerifier =
126+
new SnapshotVerifier(config, testInfo.getTestClass().get(), false);
127+
Expect expect = Expect.of(snapshotVerifier, testInfo.getTestMethod().get());
128+
expect.toMatchSnapshot("NEW");
129+
snapshotVerifier.validateSnapshots();
130+
131+
String content =
132+
new String(
133+
Files.readAllBytes(
134+
Paths.get(
135+
"src/test/java/au/com/origin/snapshots/__snapshots__/UpdateSnapshotTest.snap")),
136+
StandardCharsets.UTF_8);
137+
Assertions.assertThat(content)
138+
.isEqualTo(
139+
"au.com.origin.snapshots.UpdateSnapshotTest.canUpdateAllSnapshots=[\n"
140+
+ "OLD\n"
141+
+ "]\n"
142+
+ "\n"
143+
+ "\n"
144+
+ "au.com.origin.snapshots.UpdateSnapshotTest.canUpdateClassNameSnapshots=[\n"
145+
+ "NEW\n"
146+
+ "]\n"
147+
+ "\n"
148+
+ "\n"
149+
+ "au.com.origin.snapshots.UpdateSnapshotTest.canUpdateNoSnapshots=[\n"
150+
+ "OLD\n"
151+
+ "]");
152+
}
153+
}

0 commit comments

Comments
 (0)