Skip to content

Commit 51d9e40

Browse files
committed
Removed static references to SPI instances (Tests)
Added tests to ensure that Monetary, MonetaryFormats and MonetaryConversions are not caching SPI instances when replacing ServiceProvider using Bootstrap.init()
1 parent da8ff99 commit 51d9e40

File tree

6 files changed

+463
-2
lines changed

6 files changed

+463
-2
lines changed

pom.xml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<artifactId>money-api</artifactId>
1616
<packaging>bundle</packaging>
1717

18-
<version>1.0.1</version>
18+
<version>1.0.2</version>
1919
<name>JSR 354 (Money and Currency API)</name>
2020
<url>http://java.net/projects/javamoney</url>
2121
<inceptionYear>2012</inceptionYear>
@@ -34,6 +34,7 @@
3434
<basedir>.</basedir>
3535
<!-- Dependency versions -->
3636
<testng.version>6.8.5</testng.version>
37+
<mockito.version>1.10.19</mockito.version>
3738
</properties>
3839

3940
<organization>
@@ -399,6 +400,12 @@
399400
<version>${testng.version}</version>
400401
<scope>test</scope>
401402
</dependency>
403+
<dependency>
404+
<groupId>org.mockito</groupId>
405+
<artifactId>mockito-all</artifactId>
406+
<version>${mockito.version}</version>
407+
<scope>test</scope>
408+
</dependency>
402409
</dependencies>
403410
</dependencyManagement>
404411

@@ -408,6 +415,11 @@
408415
<artifactId>testng</artifactId>
409416
<scope>test</scope>
410417
</dependency>
418+
<dependency>
419+
<groupId>org.mockito</groupId>
420+
<artifactId>mockito-all</artifactId>
421+
<scope>test</scope>
422+
</dependency>
411423
</dependencies>
412424

413425
<build>
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* CREDIT SUISSE IS WILLING TO LICENSE THIS SPECIFICATION TO YOU ONLY UPON THE CONDITION THAT YOU
3+
* ACCEPT ALL OF THE TERMS CONTAINED IN THIS AGREEMENT. PLEASE READ THE TERMS AND CONDITIONS OF THIS
4+
* AGREEMENT CAREFULLY. BY DOWNLOADING THIS SPECIFICATION, YOU ACCEPT THE TERMS AND CONDITIONS OF
5+
* THE AGREEMENT. IF YOU ARE NOT WILLING TO BE BOUND BY IT, SELECT THE "DECLINE" BUTTON AT THE
6+
* BOTTOM OF THIS PAGE. Specification: JSR-354 Money and Currency API ("Specification") Copyright
7+
* (c) 2012-2013, Credit Suisse All rights reserved.
8+
*/
9+
package javax.money;
10+
11+
import java.util.ArrayList;
12+
import java.util.HashMap;
13+
import java.util.List;
14+
import java.util.Map;
15+
16+
import javax.money.spi.Bootstrap;
17+
import javax.money.spi.CurrencyProviderSpi;
18+
import javax.money.spi.ServiceProvider;
19+
20+
import org.mockito.Mockito;
21+
import org.testng.annotations.AfterMethod;
22+
import org.testng.annotations.BeforeMethod;
23+
24+
/**
25+
* Abstract test supporting to switch SPI implementations by using a ServiceProvider providing manually registered SPIs only.
26+
* @author Matthias Hanisch
27+
*/
28+
public abstract class AbstractDynamicServiceProviderTest {
29+
30+
private ServiceProvider originalServiceProvider;
31+
private TestServiceProvider testServiceProvider;
32+
33+
/**
34+
* Initialized Bootstrap so that default ServiceProvider is enabled. Then stores the default ServiceProvider in
35+
* {@link #originalServiceProvider}. Initializes Bootstrap with default ServiceProvider again so that it is
36+
* usable in the Test. Also initialized {@link #testServiceProvider}.
37+
*/
38+
@BeforeMethod
39+
public void prepare() {
40+
Bootstrap.getService(CurrencyProviderSpi.class);
41+
ServiceProvider empty = Mockito.mock(ServiceProvider.class);
42+
originalServiceProvider= Bootstrap.init(empty);
43+
Bootstrap.init(originalServiceProvider);
44+
testServiceProvider = new TestServiceProvider();
45+
}
46+
47+
/**
48+
* Restores the default ServiceProvider.
49+
*/
50+
@AfterMethod
51+
public void restore() {
52+
testServiceProvider.clearServices();
53+
Bootstrap.init(originalServiceProvider);
54+
}
55+
56+
protected final void initTestServiceProvider() {
57+
Bootstrap.init(testServiceProvider);
58+
}
59+
60+
protected final void initOriginalServiceProvider() {
61+
Bootstrap.init(originalServiceProvider);
62+
}
63+
64+
/**
65+
* Registers a SPI service so that it is accessible via {@link Bootstrap#getService(Class)} when using {@link #testServiceProvider}.
66+
* @param serviceType The SPI type.
67+
* @param service The SPI instance.
68+
*/
69+
protected final <T> void registerService(Class<T> serviceType, T service) {
70+
testServiceProvider.registerService(serviceType, service);
71+
}
72+
73+
class TestServiceProvider implements ServiceProvider {
74+
75+
private Map<Class<?>, List<?>> services = new HashMap<>();
76+
77+
@Override
78+
public int getPriority() {
79+
return 0;
80+
}
81+
82+
@Override
83+
public <T> List<T> getServices(Class<T> serviceType) {
84+
return (List<T>) services.get(serviceType);
85+
}
86+
87+
@Override
88+
public <T> T getService(Class<T> serviceType) {
89+
List<T> servicesOfType = getServices(serviceType);
90+
if(servicesOfType==null||servicesOfType.isEmpty()) {
91+
return null;
92+
} else {
93+
return servicesOfType.get(0);
94+
}
95+
}
96+
97+
public <T> void registerService(Class<T> serviceType, T service) {
98+
List<T> servicesOfType = (List<T>) services.get(serviceType);
99+
if(servicesOfType==null) {
100+
servicesOfType = new ArrayList<>();
101+
services.put(serviceType, servicesOfType);
102+
}
103+
servicesOfType.add(service);
104+
}
105+
106+
public void clearServices() {
107+
services.clear();
108+
}
109+
}
110+
}
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/*
2+
* CREDIT SUISSE IS WILLING TO LICENSE THIS SPECIFICATION TO YOU ONLY UPON THE CONDITION THAT YOU
3+
* ACCEPT ALL OF THE TERMS CONTAINED IN THIS AGREEMENT. PLEASE READ THE TERMS AND CONDITIONS OF THIS
4+
* AGREEMENT CAREFULLY. BY DOWNLOADING THIS SPECIFICATION, YOU ACCEPT THE TERMS AND CONDITIONS OF
5+
* THE AGREEMENT. IF YOU ARE NOT WILLING TO BE BOUND BY IT, SELECT THE "DECLINE" BUTTON AT THE
6+
* BOTTOM OF THIS PAGE. Specification: JSR-354 Money and Currency API ("Specification") Copyright
7+
* (c) 2012-2013, Credit Suisse All rights reserved.
8+
*/
9+
package javax.money;
10+
11+
import static org.mockito.Matchers.any;
12+
import static org.mockito.Matchers.anyString;
13+
import static org.mockito.Mockito.doAnswer;
14+
import static org.mockito.Mockito.doReturn;
15+
import static org.mockito.Mockito.mock;
16+
import static org.testng.Assert.assertEquals;
17+
import static org.testng.Assert.assertFalse;
18+
import static org.testng.Assert.assertNotNull;
19+
import static org.testng.Assert.assertTrue;
20+
import static org.testng.Assert.fail;
21+
22+
import java.util.Arrays;
23+
import java.util.HashSet;
24+
import java.util.List;
25+
26+
import javax.money.internal.DefaultMonetaryAmountsSingletonQuerySpi;
27+
import javax.money.internal.DefaultMonetaryAmountsSingletonSpi;
28+
import javax.money.spi.Bootstrap;
29+
import javax.money.spi.MonetaryAmountsSingletonQuerySpi;
30+
import javax.money.spi.MonetaryAmountsSingletonSpi;
31+
import javax.money.spi.MonetaryCurrenciesSingletonSpi;
32+
import javax.money.spi.MonetaryRoundingsSingletonSpi;
33+
import javax.money.spi.RoundingProviderSpi;
34+
import javax.money.spi.ServiceProvider;
35+
36+
import org.mockito.Mockito;
37+
import org.mockito.invocation.InvocationOnMock;
38+
import org.mockito.stubbing.Answer;
39+
import org.testng.annotations.Test;
40+
41+
/**
42+
* Test to ensure that singleton SPIs used by {@link Monetary} handle substitution of {@link ServiceProvider} to use when
43+
* calling {@link Bootstrap#init(javax.money.spi.ServiceProvider)}. (e.g. in an OSGI environment)
44+
* @author Matthias Hanisch
45+
*
46+
*/
47+
public class MonetaryDynamicServiceProviderTest extends AbstractDynamicServiceProviderTest {
48+
49+
50+
/**
51+
* Default SPI: {@link DefaultMonetaryCurrenciesSingletonSpi} supports currencies test1 and test2.
52+
* Dynamic SPI: Mock supporting currencies test1 and test3.
53+
* Testing following steps:
54+
* <ul>
55+
* <li>use default SPI</li>
56+
* <li>currency test1 available</li>
57+
* <li>currency test2 available</li>
58+
* <li>currency test3 <b>not</b> available</li>
59+
* <li>use dynamic SPI</li>
60+
* <li>currency test1 available</li>
61+
* <li>currency test2 <b>not</b> available</li>
62+
* <li>currency test3 available</li>
63+
* <li>use default SPI</li>
64+
* <li>currency test1 available</li>
65+
* <li>currency test2 available</li>
66+
* <li>currency test3 <b>not</b> available</li>
67+
* </ul>
68+
*
69+
*/
70+
@Test
71+
public void testMonetaryCurrenciesSingletonSpi() {
72+
// DefaultMonetaryCurrenciesSingletonSpi is used
73+
assertCurrencyAvailable("test1");
74+
assertCurrencyAvailable("test2");
75+
assertCurrencyMissing("test3");
76+
MonetaryCurrenciesSingletonSpi mockSingleton = Mockito.mock(MonetaryCurrenciesSingletonSpi.class);
77+
registerService(MonetaryCurrenciesSingletonSpi.class, mockSingleton);
78+
doAnswer(new Answer<CurrencyUnit>() {
79+
private List<String> supportedCurrencies = Arrays.asList("test1","test3");
80+
@Override
81+
public CurrencyUnit answer(InvocationOnMock invocation)
82+
throws Throwable {
83+
String currencyCode =(String)invocation.getArguments()[0];
84+
if(supportedCurrencies.contains(currencyCode)) {
85+
return new TestCurrency(currencyCode, 1,2);
86+
}
87+
throw new UnknownCurrencyException(currencyCode);
88+
}
89+
}).when(mockSingleton).getCurrency(anyString());
90+
initTestServiceProvider();
91+
// DynamicMonetaryCurrenciesSingletonSpi is used
92+
assertCurrencyAvailable("test1");
93+
assertCurrencyMissing("test2");
94+
assertCurrencyAvailable("test3");
95+
initOriginalServiceProvider();
96+
// DefaultMonetaryCurrenciesSingletonSpi is used again
97+
assertCurrencyAvailable("test1");
98+
assertCurrencyAvailable("test2");
99+
assertCurrencyMissing("test3");
100+
}
101+
102+
/**
103+
* Default SPI: {@link DefaultMonetaryAmountsSingletonSpi} supports a {@link MonetaryAmountFactory} creating instances of DummyAmount.
104+
* Dynamic SPI: Mock supporting a {@link MonetaryAmountFactory} creating a mocked {@link MonetaryAmount}
105+
* Testing following steps:
106+
* <ul>
107+
* <li>use default SPI</li>
108+
* <li>created MonetaryAmount should be a DummyAmount</li>
109+
* <li>use dynamic SPI</li>
110+
* <li>created MonetaryAmount should match the mocked Amount</li>
111+
* <li>use default SPI</li>
112+
* <li>created MonetaryAmount should be a DummyAmount</li>
113+
* </ul>
114+
*/
115+
@Test
116+
public void testMonetaryAmountsSingletonSpi() {
117+
assertTrue(Monetary.getDefaultAmountFactory().create()instanceof DummyAmount);
118+
MonetaryAmountsSingletonSpi mockSingleton = mock(MonetaryAmountsSingletonSpi.class);
119+
MonetaryAmountFactory<?> mockFactory = mock(MonetaryAmountFactory.class);
120+
MonetaryAmount mockAmount = mock(MonetaryAmount.class);
121+
doReturn(mockFactory).when(mockSingleton).getDefaultAmountFactory();
122+
doReturn(mockAmount).when(mockFactory).create();
123+
registerService(MonetaryAmountsSingletonSpi.class, mockSingleton);
124+
initTestServiceProvider();
125+
assertFalse(Monetary.getDefaultAmountFactory().create()instanceof DummyAmount);
126+
assertEquals(Monetary.getDefaultAmountFactory().create(), mockAmount);
127+
initOriginalServiceProvider();
128+
assertTrue(Monetary.getDefaultAmountFactory().create()instanceof DummyAmount);
129+
}
130+
131+
/**
132+
* Default SPI: {@link DefaultMonetaryAmountsSingletonQuerySpi} supports {@link MonetaryAmountFactoryQuery} for DummyAmount.
133+
* Dynamic SPI: Mock not supporting {@link MonetaryAmountFactoryQuery} for none MonetaryMount at all.
134+
* Testing following steps:
135+
* <ul>
136+
* <li>use default SPI</li>
137+
* <li>query for DummyAmount should be true</li>
138+
* <li>use dynamic SPI</li>
139+
* <li>query for DummyAmount should be false</li>
140+
* <li>use default SPI</li>
141+
* <li>query for DummyAmount should be true</li>
142+
* </ul>
143+
*/
144+
@Test
145+
public void testMonetaryAmountsSingletonQuerySpi() {
146+
MonetaryAmountFactoryQuery query = MonetaryAmountFactoryQueryBuilder.of()
147+
.setTargetType(DummyAmount.class).build();
148+
assertTrue(Monetary.isAvailable(query));
149+
MonetaryAmountsSingletonQuerySpi mock= mock(MonetaryAmountsSingletonQuerySpi.class);
150+
doReturn(Boolean.FALSE).when(mock).isAvailable(any(MonetaryAmountFactoryQuery.class));
151+
registerService(MonetaryAmountsSingletonQuerySpi.class, mock);
152+
initTestServiceProvider();
153+
assertFalse(Monetary.isAvailable(query));
154+
initOriginalServiceProvider();
155+
assertTrue(Monetary.isAvailable(query));
156+
}
157+
158+
/**
159+
* Default SPI: {@link DefaultMonetaryRoundingsSingletonSpi} uses {@link RoundingProviderSpi} supporting two rounding names.
160+
* Dynamic SPI: Mock supporting one rounding name.
161+
* Testing following steps:
162+
* <ul>
163+
* <li>use default SPI</li>
164+
* <li>number of rounding names should be two</li>
165+
* <li>use dynamic SPI</li>
166+
* <li>number of rounding names should be one</li>
167+
* <li>use default SPI</li>
168+
* <li>number of rounding names should be two</li>
169+
* </ul>
170+
*/
171+
@Test
172+
public void testMonetaryRoundingsSingletonSpi() {
173+
assertEquals(Monetary.getRoundingNames().size(),2);
174+
MonetaryRoundingsSingletonSpi mock = mock(MonetaryRoundingsSingletonSpi.class);
175+
doReturn(new HashSet<>(Arrays.asList("dummyRounding"))).when(mock).getRoundingNames();
176+
registerService(MonetaryRoundingsSingletonSpi.class, mock);
177+
initTestServiceProvider();
178+
assertEquals(Monetary.getRoundingNames().size(),1);
179+
initOriginalServiceProvider();
180+
assertEquals(Monetary.getRoundingNames().size(),2);
181+
}
182+
183+
private void assertCurrencyAvailable(String currency) {
184+
CurrencyUnit cur = Monetary.getCurrency(currency);
185+
assertNotNull(cur);
186+
}
187+
188+
private void assertCurrencyMissing(String currency) {
189+
try {
190+
CurrencyUnit cur = Monetary.getCurrency(currency);
191+
fail(String.format("currency %s should not be available, but got %s", currency, cur));
192+
} catch(UnknownCurrencyException ex) {
193+
ex.printStackTrace();
194+
// expected
195+
}
196+
}
197+
198+
199+
}

src/test/java/javax/money/TestCurrency.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public final class TestCurrency implements CurrencyUnit, Serializable, Comparabl
5555
/**
5656
* Private constructor.
5757
*/
58-
private TestCurrency(String code, int numCode, int fractionDigits) {
58+
TestCurrency(String code, int numCode, int fractionDigits) {
5959
this.currencyCode = code;
6060
this.numericCode = numCode;
6161
this.defaultFractionDigits = fractionDigits;

0 commit comments

Comments
 (0)