Skip to content

Commit 456ffa5

Browse files
author
Ashley Frieze
committed
Extend with suggestions from Greg
1 parent e522f26 commit 456ffa5

File tree

7 files changed

+99
-9
lines changed

7 files changed

+99
-9
lines changed

docs/JunitRules.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,6 @@ The rules are applied and the test object created just in time for each atomic t
8080
[SpringSpecJUnitStyle](../src/test/java/specs/SpringSpecJUnitStyle.java) and
8181
[SpringSpecWithRuleClasses](../src/test/java/specs/SpringSpecWithRuleClasses.java)
8282

83-
84-
85-
86-
8783
### What is Supported
8884

8985
* `@ClassRule` is applied
@@ -93,6 +89,7 @@ The rules are applied and the test object created just in time for each atomic t
9389
* `TestRule`s are applied at the level of each atomic test
9490
* `MethodRule`s are applied at the level of each atomic test
9591
* `junitMixin` is implemented to be thread-safe
92+
* `junitMixin` provides an overload for unboxing the supplier - `junitMixin(SomeClass.class, SomeInterface.class)` - if your mixin has an interface to it. See also [`let` - when get is getting you down.](VariablesAndValues.md)
9693

9794
### What is not supported
9895

docs/VariablesAndValues.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,14 @@ it("can use the object as though it was not in a supplier", () -> {
109109
assertThat(list.get(0), is("Hello"));
110110
});
111111
```
112+
113+
The `unbox` method can be used with any `Supplier<>`. There is also an overload of `let` as a short form for this:
114+
115+
```java
116+
List<String> list = let(ArrayList::new, List.class);
117+
118+
it("can use the object as though it was not in a supplier", () -> {
119+
list.add("Hello");
120+
assertThat(list.get(0), is("Hello"));
121+
});
122+
```

src/main/java/com/greghaskins/spectrum/Configure.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.greghaskins.spectrum;
22

3+
import static com.greghaskins.spectrum.Unboxer.unbox;
4+
35
import com.greghaskins.spectrum.internal.DeclarationState;
46
import com.greghaskins.spectrum.internal.configuration.BlockFocused;
57
import com.greghaskins.spectrum.internal.configuration.BlockIgnore;
@@ -11,7 +13,6 @@
1113
import com.greghaskins.spectrum.internal.junit.Rules;
1214

1315
import java.time.Duration;
14-
import java.util.concurrent.TimeUnit;
1516
import java.util.function.Supplier;
1617

1718
public interface Configure {
@@ -154,4 +155,22 @@ static FilterConfigurationChain excludeTags(String... tagsToExclude) {
154155
static <T> Supplier<T> junitMixin(final Class<T> classWithRules) {
155156
return Rules.applyRules(classWithRules, DeclarationState.instance()::addHook);
156157
}
158+
159+
/**
160+
* Uses the given class as a mix-in for JUnit rules to be applied. These rules will cascade down
161+
* and be applied at the level of specs or atomic specs. Provide a proxy to the eventual mix-in
162+
* via the interface given.
163+
*
164+
* @param classWithRules Class to create and apply rules to for each spec.
165+
* @param interfaceToReturn the type of interface the caller requires the proxy object to be
166+
* @param <T> type of the mixin object
167+
* @param <R> the required type at the consumer - allowing for generic interfaces
168+
* (e.g. <code>List&lt;String&gt;</code>)
169+
* @param <S> type of the interface common to the rules object and the proxy
170+
* @return a proxy to the rules object
171+
*/
172+
static <T extends S, R extends S, S> R junitMixin(final Class<T> classWithRules,
173+
final Class<S> interfaceToReturn) {
174+
return unbox(junitMixin(classWithRules), interfaceToReturn);
175+
}
157176
}

src/main/java/com/greghaskins/spectrum/Spectrum.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.greghaskins.spectrum;
22

3+
import static com.greghaskins.spectrum.Unboxer.unbox;
4+
35
import com.greghaskins.spectrum.dsl.specification.Specification;
46
import com.greghaskins.spectrum.internal.DeclarationState;
57
import com.greghaskins.spectrum.internal.Suite;
@@ -234,6 +236,33 @@ public static <T> Supplier<T> let(final com.greghaskins.spectrum.ThrowingSupplie
234236
return Specification.let(supplier);
235237
}
236238

239+
/**
240+
* A value that will be fresh within each spec and cannot bleed across specs. This is provided
241+
* as a proxy to a lazy loading object, with the interface given.
242+
*
243+
* <p>
244+
* Note that {@code let} is lazy-evaluated: the internal {@code supplier} is not called until the first
245+
* time it is used.
246+
* </p>
247+
*
248+
* @param <T> The type of value
249+
* @param <R> the required type at the consumer - allowing for generic interfaces
250+
* (e.g. <code>List&lt;String&gt;</code>)
251+
* @param <S> the type of the value's interface.
252+
* @param supplier {@link com.greghaskins.spectrum.ThrowingSupplier} function that either
253+
* generates the value, or throws a {@link Throwable}
254+
* @param interfaceToUse an interface type, that is supported by the supplied object and can be used
255+
* to access it through the supplier without having to call {@link Supplier#get()}
256+
* @return proxy to the object supplied, using the interface given
257+
* @see Specification#let
258+
* @see Unboxer#unbox(Supplier, Class)
259+
*/
260+
public static <T extends S, R extends S, S> R let(
261+
final com.greghaskins.spectrum.ThrowingSupplier<T> supplier,
262+
Class<S> interfaceToUse) {
263+
return unbox(let(supplier), interfaceToUse);
264+
}
265+
237266
private final Suite rootSuite;
238267

239268
/**

src/main/java/com/greghaskins/spectrum/Unboxer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public interface Unboxer {
2424
*/
2525
@SuppressWarnings("unchecked")
2626
static <T extends S, R extends S, S> R unbox(Supplier<T> supplier, Class<S> asClass) {
27-
return (R) Proxy.newProxyInstance(asClass.getClassLoader(), new Class[] {asClass},
27+
return (R) Proxy.newProxyInstance(asClass.getClassLoader(), new Class<?>[] {asClass},
2828
(Object proxy, Method method, Object[] args) -> method.invoke(supplier.get(), args));
2929
}
3030
}

src/test/java/specs/JUnitRuleExample.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,19 @@
2121

2222
@RunWith(Spectrum.class)
2323
public class JUnitRuleExample {
24+
public interface JUnitTempFolderInterface {
25+
TemporaryFolder getTempFolder();
26+
}
27+
2428
// mixins for the Spectrum native style of mixin
25-
public static class TempFolderRuleMixin {
29+
public static class TempFolderRuleMixin implements JUnitTempFolderInterface {
2630
@Rule
2731
public TemporaryFolder tempFolderRule = new TemporaryFolder();
32+
33+
@Override
34+
public TemporaryFolder getTempFolder() {
35+
return tempFolderRule;
36+
}
2837
}
2938

3039
// alternative morphology of providing a rule - see http://junit.org/junit4/javadoc/4.12/org/junit/Rule.html
@@ -95,12 +104,28 @@ public static void beforeClass() {
95104
it("has access to an initialised object", () -> {
96105
assertNotNull(tempFolderRuleMixin.get().getFolder().getRoot());
97106
});
107+
108+
describe("A spec with a rule mix-in via interface", () -> {
109+
JUnitTempFolderInterface tempFolderGetter =
110+
junitMixin(TempFolderRuleMixin.class, JUnitTempFolderInterface.class);
111+
112+
it("has access to the rule-provided object at the top level", () -> {
113+
checkCanUseTempFolderAndRecordWhatItWas(ruleProvidedFoldersSeen,
114+
tempFolderGetter.getTempFolder());
115+
});
116+
117+
});
98118
});
99119
}
100120

101121
private void checkCanUseTempFolderAndRecordWhatItWas(Set<File> filesSeen,
102122
Supplier<TempFolderRuleMixin> tempFolderRuleMixin) {
103-
assertNotNull(tempFolderRuleMixin.get().tempFolderRule.getRoot());
104-
filesSeen.add(tempFolderRuleMixin.get().tempFolderRule.getRoot());
123+
checkCanUseTempFolderAndRecordWhatItWas(filesSeen, tempFolderRuleMixin.get().tempFolderRule);
124+
}
125+
126+
private void checkCanUseTempFolderAndRecordWhatItWas(Set<File> filesSeen,
127+
TemporaryFolder folder) {
128+
assertNotNull(folder.getRoot());
129+
filesSeen.add(folder.getRoot());
105130
}
106131
}

src/test/java/specs/UnboxerSpecs.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ public class UnboxerSpecs {
3636
});
3737
});
3838

39+
describe("Using let overload", () -> {
40+
List<String> list = let(ArrayList::new, List.class);
41+
42+
it("can use the object as though it was not in a supplier", () -> {
43+
list.add("Hello");
44+
assertThat(list.get(0), is("Hello"));
45+
});
46+
});
47+
3948
describe("Using unboxer with variable", () -> {
4049
Variable<ArrayList<String>> listVariable = new Variable<>(new ArrayList<>());
4150
List<String> list = unbox(listVariable, List.class);

0 commit comments

Comments
 (0)