### Question:
FallbackJSBundleLoader extends JSBundleLoader { @Override public String loadScript(JSBundleLoaderDelegate delegate) { while (true) { try { return getDelegateLoader().loadScript(delegate); } catch (Exception e) { if (e.getMessage() == null || !e.getMessage().startsWith(RECOVERABLE)) { throw e; } mLoaders.pop(); mRecoveredErrors.add(e); FLog.wtf(TAG, "Falling back from recoverable error", e); } } } FallbackJSBundleLoader(List<JSBundleLoader> loaders); @Override String loadScript(JSBundleLoaderDelegate delegate);  }### Answer:
@Test public void firstLoaderSucceeds() { JSBundleLoader delegates[] = new JSBundleLoader[] { successfulLoader("url1"), successfulLoader("url2") }; FallbackJSBundleLoader fallbackLoader = new FallbackJSBundleLoader(new ArrayList<>(Arrays.asList(delegates))); assertThat(fallbackLoader.loadScript(null)).isEqualTo("url1"); verify(delegates[0], times(1)).loadScript(null); verify(delegates[1], never()).loadScript(null); assertThat(mLoggingDelegate.logContains( FakeLoggingDelegate.WTF, FallbackJSBundleLoader.TAG, null)) .isFalse(); }
@Test public void fallingBackSuccessfully() { JSBundleLoader delegates[] = new JSBundleLoader[] { recoverableLoader("url1", "error1"), successfulLoader("url2"), successfulLoader("url3") }; FallbackJSBundleLoader fallbackLoader = new FallbackJSBundleLoader(new ArrayList<>(Arrays.asList(delegates))); assertThat(fallbackLoader.loadScript(null)).isEqualTo("url2"); verify(delegates[0], times(1)).loadScript(null); verify(delegates[1], times(1)).loadScript(null); verify(delegates[2], never()).loadScript(null); assertThat(mLoggingDelegate.logContains( FakeLoggingDelegate.WTF, FallbackJSBundleLoader.TAG, recoverableMsg("error1"))) .isTrue(); }
@Test public void fallingbackUnsuccessfully() { JSBundleLoader delegates[] = new JSBundleLoader[] { recoverableLoader("url1", "error1"), recoverableLoader("url2", "error2") }; FallbackJSBundleLoader fallbackLoader = new FallbackJSBundleLoader(new ArrayList<>(Arrays.asList(delegates))); try { fallbackLoader.loadScript(null); fail("expect throw"); } catch (Exception e) { assertThat(e).isInstanceOf(RuntimeException.class); Throwable cause = e.getCause(); ArrayList<String> msgs = new ArrayList<>(); while (cause != null) { msgs.add(cause.getMessage()); cause = cause.getCause(); } assertThat(msgs).containsExactly( recoverableMsg("error1"), recoverableMsg("error2")); } verify(delegates[0], times(1)).loadScript(null); verify(delegates[1], times(1)).loadScript(null); assertThat(mLoggingDelegate.logContains( FakeLoggingDelegate.WTF, FallbackJSBundleLoader.TAG, recoverableMsg("error1"))) .isTrue(); assertThat(mLoggingDelegate.logContains( FakeLoggingDelegate.WTF, FallbackJSBundleLoader.TAG, recoverableMsg("error2"))) .isTrue(); }
@Test public void unrecoverable() { JSBundleLoader delegates[] = new JSBundleLoader[] { fatalLoader("url1", "error1"), recoverableLoader("url2", "error2") }; FallbackJSBundleLoader fallbackLoader = new FallbackJSBundleLoader(new ArrayList(Arrays.asList(delegates))); try { fallbackLoader.loadScript(null); fail("expect throw"); } catch (Exception e) { assertThat(e.getMessage()).isEqualTo(fatalMsg("error1")); } verify(delegates[0], times(1)).loadScript(null); verify(delegates[1], never()).loadScript(null); assertThat(mLoggingDelegate.logContains( FakeLoggingDelegate.WTF, FallbackJSBundleLoader.TAG, null)) .isFalse(); }