Source:
<code><START> public void testRefreshDoesntHang() throws IOException, InterruptedException, ExecutionException, <END> TimeoutException { setTime(2L); final int expiration = HeaderCacheElement.TOKEN_STALENESS_MS + 1; final Object lock = new String(""); Mockito.when(credentials.refreshAccessToken()).thenAnswer(new Answer<AccessToken>() { @Override public AccessToken answer(InvocationOnMock invocation) throws Throwable { synchronized (lock) { lock.wait(); } return new AccessToken("", new Date(expiration)); } }); underTest = new RefreshingOAuth2CredentialsInterceptor(executorService, credentials); final AtomicBoolean completed = new AtomicBoolean(false); Callable<Void> syncRefreshCallable = new Callable<Void>() { @Override public Void call() throws Exception { underTest.syncRefresh(); completed.set(true); return null; } }; Future<Void> future = executorService.submit(syncRefreshCallable); try { future.get(100, TimeUnit.MILLISECONDS); } catch (Exception ignored) { } synchronized(lock) { lock.notifyAll(); } future.get(1000, TimeUnit.MILLISECONDS); Assert.assertEquals(CacheState.Stale, RefreshingOAuth2CredentialsInterceptor.getCacheState(underTest.headerCache.get())); Assert.assertFalse(underTest.isRefreshing.get()); underTest.asyncRefresh(); underTest.asyncRefresh(); Thread.sleep(100); Assert.assertTrue(underTest.isRefreshing.get()); completed.set(false); future = executorService.submit(syncRefreshCallable); Thread.sleep(100); Assert.assertFalse(completed.get()); synchronized(lock) { lock.notifyAll(); } future.get(); Assert.assertTrue(completed.get()); Assert.assertFalse(underTest.isRefreshing.get()); }</code><technical_language>nit: throws Exception</technical_language>

Target:
public void testRefreshDoesntHang() throws Exception, TimeoutException { long start = HeaderCacheElement.TOKEN_STALENESS_MS * 10; setTime(start); final long expiration = start + HeaderCacheElement.TOKEN_EXPIRES_MS + 1; final Object lock = new String(""); Mockito.when(credentials.refreshAccessToken()).thenAnswer(new Answer<AccessToken>() { @Override public AccessToken answer(InvocationOnMock invocation) throws Throwable { synchronized (lock) { lock.wait(); } return new AccessToken("", new Date(expiration)); } }); Callable<Void> syncRefreshCallable = new Callable<Void>() { @Override public Void call() throws Exception { underTest.syncRefresh(); return null; } }; underTest = new RefreshingOAuth2CredentialsInterceptor(executorService, credentials); Assert.assertEquals(CacheState.Expired, RefreshingOAuth2CredentialsInterceptor.getCacheState(underTest.headerCache.get())); Future<Void> future = executorService.submit(syncRefreshCallable); Thread.yield(); synchronized(lock) { lock.notifyAll(); } future.get(1, TimeUnit.SECONDS); Assert.assertEquals(CacheState.Stale, RefreshingOAuth2CredentialsInterceptor.getCacheState(underTest.headerCache.get())); Assert.assertFalse(underTest.isRefreshing.get()); underTest.asyncRefresh(); underTest.asyncRefresh(); underTest.asyncRefresh(); future = executorService.submit(syncRefreshCallable); Thread.yield(); Assert.assertTrue(underTest.isRefreshing.get()); synchronized(lock) { lock.notifyAll(); } future.get(1, TimeUnit.SECONDS); Assert.assertFalse(underTest.isRefreshing.get()); }