Android Hilt Testing: Bind different database per test - java

I am currently writing tests for my Android application using Hilt. The App got a Room database, which I want to use in my test classes. Furthermore, I want in some test classes an empty database and in others a database with test values. My question is if I can define in my Test, which provider (empty/full DB) is used for all injections that are happening in the test class?
I tried using the #Named annotation, Qualifiers, and #BindValue. With the first two I am able to use the correct provider for my database, but I do not know how this could be used when injecting my repository. With #BindValue I am not sure if this can be used for this at all.
Here is some simplified code of my problem:
TestDatabaseModule:
#Module
#TestInstallIn(components = SingletonComponent.class, replaces = DatabaseModule.class)
public class TestDatabaseModule {
// Provide empty DB
#Singleton
#Provides
AppDatabase provideInMemoryDbEmpty(#ApplicationContext Context context) {
return Room.inMemoryDatabaseBuilder(context, AppDatabase.class).build();
}
// Provide populated DB
#Singleton
#Provides
AppDatabase provideInMemoryDbFull(#ApplicationContext Context context, Provider<AppDatabase> databaseProvider) {
// AppDatabasePopulateCallback adds some test data
return Room.inMemoryDatabaseBuilder(context, AppDatabase.class).addCallback(new AppDatabasePopulateCallback(databaseProvider)).build();
}
}
Module providing DAO and repository
#InstallIn(SingletonComponent.class)
#Module
public class AccessModule {
#Singleton
#Provides
MyDao provideMyDao(AppDatabase appDatabase) {
return appDatabase.myDao();
}
#Provides
MyRepository providesMyRepository(MyDao myDao) {
return new MyRepository(myDao);
}
}
Test Class one:
#HiltAndroidTest
#SmallTest
public class TestOne {
#Rule
public HiltAndroidRule hiltRule = new HiltAndroidRule(this);
#Rule
public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule();
// Inject repository that uses empty DB
#Inject
MyRepository myRepository;
#Before
public void createDb() {
hiltRule.inject();
}
#Test
public void testOne() {
// Use my repository with the empty database
}
Test class two:
#HiltAndroidTest
#SmallTest
public class TestTwo {
#Rule
public HiltAndroidRule hiltRule = new HiltAndroidRule(this);
#Rule
public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule();
// Inject repository that uses populated DB
#Inject
MyRepository myRepository;
#Before
public void createDb() {
hiltRule.inject();
}
#Test
public void testTwo() {
// Use my repository with the full database
}
So in the TestOne class, I want to use the repository that can access the empty database, and in the TestTwo class the repository that uses the database with test data.
I know I could populate the database in every test I want to use the data. That is what I will do if it is not possible with Hilt. I am glad for any advice to solve my problem or someone answering that I am on the wrong track if it is not possible.
Thank you!

Related

How to set up unit test for service with dependency?

I have a service interface:
interface MyService
{
int getItem();
}
and a implementation:
#Service
class MyServiceImpl : implements MyService {
public MyServiceImpl(List<Loader> loaders) {
}
public int getItem() {
Loader loader = getTheLoader();
return loader.getItem();
}
}
I want to unit test MyServiceImpl. There's more to it then just forwarding the getItem() call to the loader, so I'd think I'd want to test the actual class. I don't think I'd want to test an actual loader, because that would be more of an integration test, yeah?
So, I'm not sure if this is the right approach? To create a real instance of the Impl class and then create a real instance of the TestLoader?
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class MyServiceTests {
private MyService myService;
static class TestLoader implements Loader<String, Object> {
#Override
int getItem() {
}
}
#BeforeAll
void init() {
myService= new MyServiceImpl(List.of(new
TestLoader()));
}
I do want to capture call counts to the TestLoader::getItem, etc.
Kinda seems like I need to create a mock loader instead of a real loader to capture the call counts?
But its not clear how I would mock a Loader<String, Object> and override the getItem() class.
If you want to test both the class and the loader you have to do an integration test, you are right, but for unit test you have to mock other classes (that you are going to test next or you have already tested in other unit tests)
You can mock the loader (you can use mockito) and create a new list containing the mock
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class MyServiceTests {
private MyService myService;
#Mock
private Loader loader;
#BeforeAll
void init() {
myService= new MyServiceImpl(List.of(loader));
}
}

Java spring Mockito does not mock MongoDb service

I have a service
#Service
public class ShopServiceImpl implements ShopService {
private ShopRepository shopRepository;
public ShopServiceImpl (ShopRepository shopRepository) {
this.shopRepository= shopRepository;
}
...
}
and I want to write test with mocked Mongo database to test the CRUD methods.
#RunWith(MockitoJUnitRunner.class)
public class GetShopServiceTest {
#Mock
private ShopRepository ShopRepository;
#InjectMocks
private ShopServiceImpl shopService;
#Test
public void randomTest() {
...
Shop shopSaved = shopService.save(shop);
List<Shop> shopsRetrieved = shopService.listShops();
}
}
But this mocked service does not save or retrieve saved record. I do not have any configurations that relate to mocked mongo db, but tried them and they didin't work. What to do?
You can use #DataMongoTest.
Sample Implementation

Mockito when isn't replacing original method behaviour

I got 2 modules User and Email, both of them have 1 entry point which is a facade, rest is package scoped. The configuration is done in 2 classes
#Configuration
class UserConfiguration {
#Bean
UserFacade userFacade(UserRepository repository, EmailFacade emailFacade) {
return new UserFacade(repository, emailFacade);
}
}
#Configuration
class EmailConfiguration {
#Bean
EmailFacade emailFacade(EmailSender emailSender) {
return new EmailFacade(emailSender);
}
}
Now, I want to write tests that don't require Spring to start. I implemented a simple InMemoryRepository to make this happen
#RunWith(MockitoJUnitRunner.class)
public class RegisterUserTest {
#Mock
private EmailFacade emailFacade = new EmailFacade(new FakeEmailSender());
#InjectMocks
private UserFacade userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository(), emailFacade);
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
}
I need some fake objects to instantiate EmailFacade so I wrote fake implementation
public class FakeEmailSender implements EmailSender {
#Override
public void sendEmail(EmailMessage emailMessage) throws RuntimeException {
}
}
In that scenario, I'm testing User domain, so I want to mock Email anyways.
I wrote a test to check if it works
#Test
public void shouldReturnSendingFailed() {
Mockito.when(emailFacade.sendUserVerificationEmail(Mockito.any())).thenReturn(Either.left(EmailError.SENDING_FAILED));
assertThat(userFacade.registerNewUser(RegisterUserDto.builder()
.username(USERNAME_4)
.email(VALID_EMAIL)
.password(VALID_PASSWORD).build()).getLeft(), is(EmailError.SENDING_FAILED));
}
But it isn't... after running this test I got
java.util.NoSuchElementException: getLeft() on Right
edit#
regiserNewUser() method
Either<DomainError, SuccessMessage> register(RegisterUserDto registerUserDto) {
if(userRepository.findUser(registerUserDto.getUsername()).isPresent())
return Either.left(UserError.USERNAME_ALREADY_EXISTS);
var userCreationResult = User.createUser(registerUserDto);
var savedUser = userCreationResult.map(this::saveUser);
var emailDto = savedUser.map(this::createVerificationEmail);
return emailDto.isRight() ? emailFacade.sendUserVerificationEmail(emailDto.get())
: Either.left(emailDto.getLeft());
}
Edit2#
With following test configuration
#RunWith(MockitoJUnitRunner.class)
public class RegisterUserTest {
#Mock
private EmailFacade emailFacade;
#InjectMocks
private UserFacade userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository(), emailFacade);
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
}
I got nullpointer here, last line of registerNewUser().
Try running this code
#RunWith(MockitoJUnitRunner.class)
public class RegisterUserTest {
#Mock
private EmailFacade emailFacade;
private UserFacade userFacade;
#Before
public void setUp() {
userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository(), emailFacade);
}
}
There are a few issues with your code:
You initialize your mocks twice. You don’t need to call initMocks in the setUp method if you are using Mockito runner
You are trying to inject mocks to already initialized object. But the field you are trying to inject is also passed to the constructor. Please read #InjectMocks doc, to check the strategies used to inject the mocks:
constructor (not used here, already initialized object)
setter (do you have one?)
field (is it not final)
There are details to each strategy (see my questions above). If no staregy is matched, Mockito will fail silently. The fact that you are passing an object in constructor, and rely on setter or field injection afterwards makes this code unnecesarily complex.

How to Mock an injected object that is not declared in Module?

For a dagger2 module
#Module
public class MyModule {
#Provides #Singleton public RestService provideRestService() {
return new RestService();
}
#Provides #Singleton public MyPrinter provideMyPrinter() {
return new MyPrinter();
}
}
We could have the test module as Test
public class TestModule extends MyModule {
#Override public MyPrinter provideMyPrinter() {
return Mockito.mock(MyPrinter.class);
}
#Override public RestService provideRestService() {
return Mockito.mock(RestService.class);
}
}
However if for a class as below that is not declared in the dagger module...
public class MainService {
#Inject MyPrinter myPrinter;
#Inject public MainService(RestService restService) {
this.restService = restService;
}
}
How do I create a mock of MainService as above.
Note, I'm not planning to perform test for MainService as per share in https://medium.com/#fabioCollini/android-testing-using-dagger-2-mockito-and-a-custom-junit-rule-c8487ed01b56#.9aky15kke, but instead, my MainService is used in another normal class that I wanted to test. e.g.
public class MyClassDoingSomething() {
#Inject MainService mainService;
public MyClassDoingSomething() {
//...
}
// ...
public void myPublicFunction() {
// This function uses mainService
}
}
This is definitely not answering your question, but in my honest opinion it is related, it's helpful and too big for a comment.
I'm often facing this question and I end always doing "Constructor dependency injection". What this means is that I no longer do field injection by annotating the field with #Inject but pass the dependencies in the constructor like so:
public class MyClassDoingSomething implements DoSomethig {
private final Service mainService;
#Inject
public MyClassDoingSomething(Service mainService) {
this.mainService = mainService;
}
}
Notice how the constructor now receives the parameter and sets the field to it and is also annotated with #Inject? I also like to make these classes implement an interface (also for MyService) - Amongst several other benefits I find it makes the dagger module easier to write:
#Module
public class DoSomethingModule {
#Provides #Singleton public RestService provideRestService() {
return new RestService();
}
#Provides #Singleton public MyPrinter provideMyPrinter() {
return new MyPrinter();
}
#Provides #Singleton public Service provideMyPrinter(MyService service) {
return service;
}
#Provides #Singleton public DoSomethig provideMyPrinter(MyClassDoingSomething something) {
return something;
}
}
(This assumes that MyService implements or extends Service)
By now it seems you already know that dagger is able to figure out the dependency graph by itself and build all the objects for you. So what about unit testing the class MyClassDoingSomething? I don't even use dagger here. I simply provide the dependencies manually:
public class MyClassDoingSomethingTest {
#Mock
Service service;
private MyClassDoingSomething something;
#Before
public void setUp() throws Exception {
MockitoAnnotations.init(this);
something = new MyClassDoingSomething(service);
}
// ...
}
As you see, the dependency is passed through the constructor manually.
Obviously this doesn't work if you're coding something that doesn't have a constructor that can be invoked by you. Classical examples are android activities, fragments or views. There are ways to achieve that, but personally I still think you can somehow overcome this without dagger. If you are unit testing a view that has a field #Inject MyPresenter myPresenter, usually this field will have package access that works fine in the tests:
public class MyViewTest {
#Mock MyPresenter presenter;
private MyView view;
#Before
public void setUp() throws Exception {
MockitoAnnotations.init(this);
view.myPresenter = presenter;
}
}
Note that this only works if both MyViewTest and MyView are in the same package (which often is the case in android projects).
At the end of the day if you still want to use dagger for the tests, you can always create "test" modules and components that can inject by declaring methods in the component like:
#Inject
public interface MyTestComponent {
void inject(MyClassDoingSomething something);
}
I find this approach ok-ish, but throughout my development years I prefer the first approach. This also has reported issues with Robolectric that some setup in the build.gradle file is required to actually make the dagger-compiler run for the tests so the classes are actually generated.

Spring: create mocks instead of real objects

I'm using Spring annotation based configuration in my Play application.
Controllers and DAOs are Spring beans. Controller and DAO layers are defined with different Spring profiles and each layer could be disabled separately.
I'd like to test controller layer in isolation from DAO layer. I've disabled DAO profile and redefined each of DAO beans as a Mockito mock. From functional point of view it works fine, the only thing I don't like is defining mocks manually like this:
#Configuration
#Import(AppContext.class)
public class TestAppContext {
#Bean
public DaoA getDaoA(){
return mock(DaoA.class);
}
//... all dependencies are re-defined manually
}
Is there a way to define package (like with #ComponentScan annotation)
and get all beans in that package as mocks instead of real objects?
UPD:
I'm running tests with FakeApplication (https://www.playframework.com/documentation/2.0/api/java/play/test/FakeApplication.html), so context is started not in the test level, but inside fake application startup.
public class ControllerTest extends WithApplication {
#Before
public void setUp() throws Exception {
start(fakeApplication(new GlobalSettings(){
private ApplicationContext appContext;
public void onStart(Application app) {
appContext = new AnnotationConfigApplicationContext(TestAppContext.class);
}
#Override
public <A> A getControllerInstance(Class<A> clazz) throws Exception {
return appContext.getBean(clazz);
}
}));
}
...
}
I did it like this because I wan't to make the test more reliable and test how controller works in real environment:
#Test
public void testControllerMethod() {
Result result = route(fakeRequest(GET, "/controller/method"));
assertThat(result).is(...);
}
If the number of dependencies you need to mock is huge, you can also use spring-auto-mock.
#ContextConfiguration(classes = { AutoMockRegistryPostProcessor.class, RestOfClasses.class, ... })
#RunWith(SpringJUnit4ClassRunner.class)
public class YourTest {
...
}
As you are creating the ApplicationContext on your own, you can register the postprocessor programmatically:
public void onStart(Application app) {
appContext = new AnnotationConfigApplicationContext(TestAppContext.class);
appContext.getBeanFactory().addBeanPostProcessor(new AutoMockRegistryPostProcessor())
}
Mark your unit-test with #RunWith(SpringJUnit4ClassRunner.class)
Mark your tested class as #InjectMock
Mark you Dao class as #Mock
Make use of Mockito in your project

Categories

Resources