1 /* 2 * Copyright (C) 2020 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dagger.hilt.android.testing; 18 19 import static androidx.test.core.app.ApplicationProvider.getApplicationContext; 20 import static com.google.common.truth.Truth.assertThat; 21 import static org.junit.Assert.assertThrows; 22 23 import androidx.test.core.app.ApplicationProvider; 24 import androidx.test.ext.junit.runners.AndroidJUnit4; 25 import dagger.hilt.EntryPoint; 26 import dagger.hilt.EntryPoints; 27 import dagger.hilt.InstallIn; 28 import dagger.hilt.components.SingletonComponent; 29 import java.util.concurrent.atomic.AtomicReference; 30 import org.junit.Rule; 31 import org.junit.Test; 32 import org.junit.rules.RuleChain; 33 import org.junit.rules.TestRule; 34 import org.junit.runner.RunWith; 35 import org.junit.runners.model.Statement; 36 import org.robolectric.annotation.Config; 37 38 @HiltAndroidTest 39 @RunWith(AndroidJUnit4.class) 40 @Config(application = HiltTestApplication.class) 41 public final class DelayComponentReadyTest { 42 private static final String EXPECTED_VALUE = "expected"; 43 44 @EntryPoint 45 @InstallIn(SingletonComponent.class) 46 public interface FooEntryPoint { foo()47 String foo(); 48 } 49 50 // If true, verifies that HiltAndroidRule threw an IllegalStateException 51 private boolean verifyTestRuleThrew = false; 52 53 // A test rule that wraps HiltAndroidRule to verify it throws 54 private final TestRule exceptionVerifyingRule = 55 (base, description) -> { 56 return new Statement() { 57 @Override 58 public void evaluate() throws Throwable { 59 AtomicReference<IllegalStateException> caught = new AtomicReference<>(); 60 try { 61 base.evaluate(); 62 } catch (IllegalStateException e) { 63 caught.set(e); 64 if (!verifyTestRuleThrew) { 65 throw e; 66 } 67 } 68 if (verifyTestRuleThrew) { 69 IllegalStateException expected = caught.get(); 70 if (expected == null) { 71 throw new AssertionError("Did not throw expected expection"); 72 } 73 assertThat(expected) 74 .hasMessageThat() 75 .isEqualTo("Used delayComponentReady(), but never called componentReady()"); 76 } 77 } 78 }; 79 }; 80 81 private final HiltAndroidRule rule = new HiltAndroidRule(this).delayComponentReady(); 82 83 @Rule public final RuleChain ruleChain = RuleChain.outerRule(exceptionVerifyingRule).around(rule); 84 85 @BindValue String foo; 86 87 @Test testLateBindValue()88 public void testLateBindValue() throws Exception { 89 AtomicReference<String> fooRef = new AtomicReference<>(); 90 OnComponentReadyRunner.addListener( 91 ApplicationProvider.getApplicationContext(), 92 FooEntryPoint.class, 93 entryPoint -> fooRef.set(entryPoint.foo())); 94 95 // Test that setting the listener before the component is ready doesn't run the listener. 96 assertThat(fooRef.get()).isNull(); 97 98 foo = EXPECTED_VALUE; 99 rule.componentReady().inject(); 100 assertThat(EntryPoints.get(getApplicationContext(), FooEntryPoint.class).foo()) 101 .isEqualTo(EXPECTED_VALUE); 102 } 103 104 @Test testUnitializedBindValue_fails()105 public void testUnitializedBindValue_fails() throws Exception { 106 OnComponentReadyRunner.addListener( 107 ApplicationProvider.getApplicationContext(), FooEntryPoint.class, FooEntryPoint::foo); 108 109 // foo not set 110 NullPointerException expected = assertThrows(NullPointerException.class, rule::componentReady); 111 // This is not the best error message, but it is equivalent to the message from a regular 112 // (non-delayed) @BindValue returning null; 113 assertThat(expected) 114 .hasMessageThat() 115 .isEqualTo("Cannot return null from a non-@Nullable @Provides method"); 116 } 117 118 @Test testDoubleComponentReady_fails()119 public void testDoubleComponentReady_fails() throws Exception { 120 foo = EXPECTED_VALUE; 121 rule.componentReady(); 122 IllegalStateException expected = 123 assertThrows(IllegalStateException.class, rule::componentReady); 124 assertThat(expected).hasMessageThat().isEqualTo("Called componentReady() multiple times"); 125 } 126 127 @Test testMissingComponentReady_fails()128 public void testMissingComponentReady_fails() throws Exception { 129 // componentReady not called 130 foo = EXPECTED_VALUE; 131 IllegalStateException expected = assertThrows(IllegalStateException.class, rule::inject); 132 assertThat(expected) 133 .hasMessageThat() 134 .isEqualTo("Called inject() before calling componentReady()"); 135 } 136 137 @Test testDelayComponentReadyAfterStart_fails()138 public void testDelayComponentReadyAfterStart_fails() throws Exception { 139 IllegalStateException expected = 140 assertThrows(IllegalStateException.class, rule::delayComponentReady); 141 assertThat(expected) 142 .hasMessageThat() 143 .isEqualTo("Called delayComponentReady after test execution started"); 144 // Prevents failure due to never calling componentReady() 145 foo = EXPECTED_VALUE; 146 rule.componentReady(); 147 } 148 149 @Test neverCallsComponentReady_fails()150 public void neverCallsComponentReady_fails() throws Exception { 151 // Does not call componentReady() 152 verifyTestRuleThrew = true; 153 } 154 } 155