Last active
June 1, 2022 12:52
-
-
Save kingargyle/b28316dc158fccb66dc2d789ecb40bf5 to your computer and use it in GitHub Desktop.
Example of Unit Testing classes with Hilt @EntryPoints
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class SchoolViewHolder extends RecyclerView.ViewHolder { | |
private ItemSchoolBinding binding; | |
// @VisibleForTesting | |
// protected EventBus eventBus = EventBus.getDefault(); | |
private EventBus eventBus; | |
public SchoolViewHolder(@NonNull View itemView) { | |
super(itemView); | |
binding = ItemSchoolBinding.bind(itemView); | |
inject(); | |
} | |
/** | |
* Injection magic happens here. We need to use @EntryPoint Accessors here since we can't | |
* use an @AndroidEntryPoint as Google doesn't support ViewHolders with that point. The | |
* work around is to use an EntryPointAccessor and inject manually (i.e. Bind them manually). | |
* This is no different than if you could have gotten a Provides or Lazy injected into the class | |
* and had to use the .get() method on those to actually get the value. | |
* | |
* What needs to be injected needs to be defined with an EntryPoint annotated interface. | |
* | |
* Note that EntryPointAccessors should only be used when you can't inject things with a | |
* Constructor and a @Provides in a module. | |
*/ | |
private void inject() { | |
InjectSchoolViewHolder inject = EntryPointAccessors.fromApplication(itemView.getContext().getApplicationContext(), InjectSchoolViewHolder.class); | |
eventBus = inject.injectEventBus(); | |
} | |
public void bind(@NonNull School school) { | |
binding.itemSchoolName.setText(school.getSchoolName()); | |
binding.itemSchoolAddress.setText(school.getPrimaryAddress()); | |
binding.itemSchoolCity.setText(school.getCity()); | |
binding.itemSchoolState.setText(school.getState()); | |
binding.itemSchoolPostalcode.setText(school.getPostalCode()); | |
binding.getRoot().setOnClickListener(v -> { | |
SchoolCardClickedEvent event = new SchoolCardClickedEvent(school.getDbn()); | |
eventBus.post(event); | |
}); | |
} | |
/** | |
* The magic that emulates a an @Inject annotation | |
*/ | |
@EntryPoint | |
@InstallIn({ApplicationComponent.class}) | |
interface InjectSchoolViewHolder { | |
EventBus injectEventBus(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Updated: 08/07/2020 | |
* | |
* This updates the test so that the EventBus can be injected into the class instead | |
* of depending directly on the test. This leverages the Hilt @EntryPoint annotation | |
* to get the necessary. It has to be run as a Robolectric Test, and the underlying | |
* class being tested needs to have access to the the Various contexts for either Views, | |
* Fragments, or Activities. | |
* | |
* You setup the tests as any other Hilt test, and must run with the appropriate Test | |
* Application. | |
* | |
*/ | |
@HiltAndroidTest | |
@UninstallModules(EventModule.class) | |
@RunWith(AndroidJUnit4.class) | |
@Config(application = HiltTestApplication.class) | |
public class SchoolViewHolderTest { | |
@Rule | |
public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); | |
@Rule | |
public HiltAndroidRule hiltAndroidRule = new HiltAndroidRule(this); | |
@Mock | |
EventBus mockEventBus; | |
private SchoolViewHolder viewHolder; | |
private ItemSchoolBinding binding; | |
@Before | |
public void setUp() { | |
ContextThemeWrapper context = new ContextThemeWrapper(ApplicationProvider.getApplicationContext(), R.style.Theme_MyApp); | |
LayoutInflater layoutInflater = LayoutInflater.from(context); | |
binding = ItemSchoolBinding.inflate(layoutInflater); | |
viewHolder = new SchoolViewHolder(binding.getRoot()); | |
} | |
@Test | |
public void bindSetsExpectedValues() { | |
School school = new School("1234", "Test School", "1234 Somewhere", "Somewhere", "CA", "43235"); | |
viewHolder.bind(school); | |
assertThat(binding.itemSchoolPostalcode).hasText("43235"); | |
assertThat(binding.itemSchoolAddress).hasText("1234 Somewhere"); | |
assertThat(binding.itemSchoolCity).hasText("Somewhere"); | |
assertThat(binding.itemSchoolState).hasText("CA"); | |
assertThat(binding.itemSchoolName).hasText("Test School"); | |
} | |
@Test | |
public void clickingOnTheCardSendsExpectedEvent() { | |
School school = new School("1234", "Test School", "1234 Somewhere", "Somewhere", "CA", "43235"); | |
SchoolCardClickedEvent event = new SchoolCardClickedEvent(school.getDbn()); | |
//viewHolder.eventBus = mockEventBus; | |
viewHolder.bind(school); | |
binding.itemSchoolCard.performClick(); | |
verify(mockEventBus).post(event); | |
} | |
@Module | |
@InstallIn(ApplicationComponent.class) | |
class TestModule { | |
@Provides | |
EventBus providesEventBus() { | |
return mockEventBus; | |
} | |
} | |
} |
Do you have any suggestions for doing similar thing in tests for a helper class where we don't want to run the inject method when creating the helper class object or by
spy
and just use mocks for the dependencies (eventBus
in this case)? Thanks in advance
If it is a unit test, I'd try and mock out the Helper class, using mockk, to mock the Object, or static mocks. Avoid calling the real class all together.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Do you have any suggestions for doing similar thing in tests for a helper class where we don't want to run the inject method when creating the helper class object or by
spy
and just use mocks for the dependencies (eventBus
in this case)? Thanks in advance