Skip to content

Instantly share code, notes, and snippets.

@kingargyle
Last active June 1, 2022 12:52
Show Gist options
  • Save kingargyle/b28316dc158fccb66dc2d789ecb40bf5 to your computer and use it in GitHub Desktop.
Save kingargyle/b28316dc158fccb66dc2d789ecb40bf5 to your computer and use it in GitHub Desktop.
Example of Unit Testing classes with Hilt @EntryPoints
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();
}
}
/**
* 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;
}
}
}
@kumardadi
Copy link

kumardadi commented May 26, 2022

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

@kingargyle
Copy link
Author

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