I want to unit test a repository which depends on LocationLiveData class:
public class LocationLiveData extends LiveData<LocationData> {
private Context mContext;
private LocationCallback locationCallback = new LocationCallback(){
#Override
public void onLocationResult(LocationResult locationResult) {
...
setValue(locationData);
}
};
#Inject
public LocationLiveData(Context context) {
mContext = context;
...
}
...
}
How can I make the mock act like liveData which emits a LocationData object after I called setValue(someLocationDataInstance)?
#RunWith(JUnit4.class)
public class LocationRepoImplTest {
#Rule
public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule();
private LocationRepo mLocationRepo;
private LocationLiveData mlocationLiveData;
#Before
public void setUp() throws Exception {
mlocationLiveData = mock(LocationLiveData.class);//(LocationLiveData) new MutableLiveData<LocationData>();
mLocationRepo = new LocationRepoImpl(mlocationLiveData);
}
#Test
public void getUserPosition() throws Exception {
LiveData<LatLng> result = mLocationRepo.getUserPosition();
Observer observer = mock(Observer.class);
result.observeForever(observer);
//how can I setValue for mLocationLiveData here?
//e.g this way: mLocationLiveData.setValue(new LocationData(TestUtil.posUser, (float) 10.0));
assertThat(result.getValue(), is(TestUtil.posUser));
}
}
Update 1: I want to test following repository:
public class LocationRepoImpl implements LocationRepo {
private LocationLiveData mLocationLiveData;
#Inject
public LocationRepoImpl(LocationLiveData locationLiveData) {
mLocationLiveData = locationLiveData;
}
#Override
public LiveData<LatLng> getUserPosition() {
return Transformations.map(mLocationLiveData, LocationData::getLatLng);
}
}
Related
I am trying to instantiate UserViewModel in my activity however it keeps giving me a java.lang.RuntimeException: Cannot create an instance of viewmodel class kindly assist.
This is how my ViewModel looks like
public class UserViewModel extends AndroidViewModel {
private NodeAuthService api;
private SharedPreferences pref;
private static MutableLiveData<List<User>> userDetails = new MutableLiveData<>();
public UserViewModel(#NonNull Application application) {
super(application);
api = AuthRetrofitClient.getInstance().create(NodeAuthService.class);
}
private String email = pref.getString("email", "");
public void loadUser(){
Call<List<User>> call;
call = api.getUser(email);
call.enqueue(new Callback<List<User>>() {
#Override
public void onResponse(Call<List<User>> call, Response<List<User>> response) {
userDetails.postValue(response.body());
}
#Override
public void onFailure(Call<List<User>> call, Throwable t) {
Log.d("USER",t.getMessage());
}
});
}
public MutableLiveData<List<User>>getUserDetails(){
return userDetails;
}
}
This is how my activity is setup
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.navigation_drawer);
String nameVm;
userViewModel = ViewModelProviders.of(this).get(UserViewModel.class);
userViewModel.loadUser();
userViewModel.getUserDetails().observe(this, new Observer<List<User>>() {
#Override
public void onChanged(List<User> users) {
if (users != null){
for (int i = 0; i<users.size(); i++){
nameVm = String.valueOf(users.get(0));
}
}
}
});
}
Create ViewModelFactory class
public class MyViewModelFactory implements ViewModelProvider.Factory {
private Application mApplication;
public MyViewModelFactory(Application application) {
mApplication = application;
}
#Override
public <T extends ViewModel> T create(Class<T> modelClass) {
// Replace UserViewModel → with whatever or however you create your ViewModel
return (T) new UserViewModel(mApplication);
}
}
and init ViewModel like
UserViewModel myViewModel = ViewModelProviders.of(this, new MyViewModelFactory(this.getApplication())).get(UserViewModel.class);
I have done like docs here but Live data'a value is not changing. Please tell me what am i doing wrong.
MainActivity
public class MainActivity extends AppCompatActivity {
private NameViewModel mModel;
private ActivityMainBinding binding;
int index = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.button.setOnClickListener((v) -> {
mModel.getCurrentName().setValue("Test");
});
mModel = ViewModelProviders.of(this).get(NameViewModel.class);
final Observer<String> nameObserver = (text) -> {
binding.textInputLayout.getEditText().setText(text);
};
mModel.getCurrentName().observe(this, nameObserver);
}
}
NameViewModel.java
public class NameViewModel extends ViewModel {
private MutableLiveData<String> mCurrentName;
public MutableLiveData<String> getCurrentName() {
if (mCurrentName == null) {
return new MutableLiveData<>();
}
return mCurrentName;
}
}
This is because, your logic returns new instance of mCurrentName each time. Please use the following function.
public class NameViewModel extends ViewModel {
private MutableLiveData<String> mCurrentName;
public MutableLiveData<String> getCurrentName() {
// Ensure there is only 1 instance of mCurrentName
if (mCurrentName == null) {
mCurrentName = new MutableLiveData<>();
}
return mCurrentName;
}
}
A much better and safer way (reduce chance of making such mistake), is to initialize mCurrentName in constructor, and mark it as final.
public class NameViewModel extends ViewModel {
private final MutableLiveData<String> mCurrentName;
public NameViewModel() {
mCurrentName = new MutableLiveData<>();
}
public MutableLiveData<String> getCurrentName() {
return mCurrentName;
}
}
I am trying to inject context in my Interactor class which is giving me a null pointer exception.
I have used the MVP pattern and I am trying to get access to the context in my non-activity class.
I am not really sure if this is the best technique used.
Module:
#Module
public class ContextModule {
private final Context context;
public ContextModule(Context context) {
this.context = context;
}
#Singleton
#Provides
public Context getContext() {
return this.context;
}
}
Component:
#Singleton
#Component(modules = {ContextModule.class})
public interface AppComponent {
void inject(MainActivity mainActivity);
}
App
public class App extends Application {
private AppComponent appComponent;
#Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.builder()
.contextModule(new ContextModule(this))
.build();
}
public AppComponent getAppComponent() {
return appComponent;
}
}
MainActivity
public class MainActivity extends AppCompatActivity implements
TaskContract.IMainView {
#Inject
MainInteractor mainInteractor;
private MainPresnter mainPresnter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((App) getApplication()).getAppComponent().inject(this);
mainPresnter = new MainPresnter(this);
}
#Override
public void getRandomNumber(int rNum) {
Toast.makeText(this, "" + rNum, Toast.LENGTH_SHORT).show();
}
#Override
protected void onResume() {
super.onResume();
mainPresnter.fetchFromService();
}
}
Presenter
public class MainPresnter implements TaskContract.IMainPresenter,
TaskContract.OnTaskCompletionResult {
private TaskContract.IMainView mainView;
private MainInteractor mainInteractor;
public MainPresnter(TaskContract.IMainView mainView) {
this.mainView = mainView;
mainInteractor = new MainInteractor(this);
}
#Override
public void fetchFromService() {
mainInteractor.callService();
}
#Override
public void onSuccess(int rNum) {
mainView.getRandomNumber(rNum);
}
}
Interactor
public class MainInteractor implements TaskContract.IMainInteractor {
private static final int JOB_ID = 100 ;
private Context context;
#Inject
public MainInteractor(Context context) {
this.context = context;
}
public MainInteractor(TaskContract.OnTaskCompletionResult completionListener)
{
TaskService.setCompletionListener(completionListener);
}
#Override
public void callService() {
JobInfo jobInfo = new JobInfo.Builder(JOB_ID,
new ComponentName(context, TaskService.class))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPeriodic(10000)
.build();
JobScheduler jobScheduler = (JobScheduler)
context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(jobInfo);
}
}
Gradle
implementation 'com.google.dagger:dagger-android:2.11'
annotationProcessor 'com.google.dagger:dagger-compiler:2.11'
You don't inject the Interactor within your Presenter - therefore it won't have a context.
You could probably restructure your Presenter to require the Interactor as a dependency - this would also mean you'd need to restructure how the completion listener is set.
Looking at this example: https://github.com/googlesamples/android-architecture-components/tree/master/GithubBrowserSample
I've implemented the same pattern in one of my side projects however, I'm facing difficulties getting tests to work as expected.
I'm trying to test my one of my repository classes. The test checks if the repository fetches data from the api and if the value of the observer changes.
Here is the test class
#RunWith(JUnit4.class)
public class TimelineRepositoryTest {
private SharedPreferences sharedPreferences;
private DatabaseDao databaseDao;
private ApiService apiService;
private TimelineRepository timelineRepository;
#Rule
public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule();
#Before
public void setup() {
sharedPreferences = mock(SharedPreferences.class);
databaseDao = mock(DatabaseDao.class);
apiService = mock(ApiService.class);
timelineRepository = new TimelineRepository(apiService, sharedPreferences, databaseDao);
timelineRepository.appExecutors = new InstantAppExecutors();
}
#Test
public void fetchTimelineWithForceFetch() {
TimelineResponse timelineResponse = new TimelineResponse();
when(sharedPreferences.getLong(PreferenceUtils.PREFERENCE_LAST_TIMELINE_REFRESH, 0)).thenReturn(0L);
when(apiService.retrieveTimeline()).thenReturn(ApiUtil.successCall(timelineResponse));
MutableLiveData<List<Event>> dbData = new MutableLiveData<>();
when(databaseDao.loadEvents()).thenReturn(dbData);
Observer observer = mock(Observer.class);
timelineRepository.getTimelineEvents().observeForever(observer);
verify(observer).onChanged(Resource.loading(null));
verify(observer).onChanged(Resource.success(new ArrayList<Event>());
}
}
Also, here is the actual repository class:
public class TimelineRepository {
#Inject AppExecutors appExecutors;
#Inject #Named("timelineRefreshDurationInMillis") long timelineRefreshDurationInMillis;
private final DatabaseDao databaseDao;
private final SharedPreferences sharedPreferences;
private final ApiService apiService;
public TimelineRepository(ApiService apiService, SharedPreferences sharedPreferences, DatabaseDao databaseDao) {
this.apiService = apiService;
this.sharedPreferences = sharedPreferences;
this.databaseDao = databaseDao;
}
public LiveData<Resource<List<Event>>> getTimelineEvents() {
return new NetworkBoundResource<List<Event>, TimelineResponse>(appExecutors) {
#Override
protected void saveCallResult(#NonNull TimelineResponse timelineResponse) {
if (timelineResponse.events != null) {
databaseDao.saveEvents(timelineResponse.events);
}
PreferenceUtils.storeLastTimelineRefreshTimeInMillis(sharedPreferences, System.currentTimeMillis());
}
#Override
protected boolean shouldFetch(#Nullable List<Event> data) {
return System.currentTimeMillis() - PreferenceUtils.getLastTimelineRefreshTimeInMillis(sharedPreferences) > timelineRefreshDurationInMillis;
}
#NonNull
#Override
protected LiveData<List<Event>> loadFromDb() {
return databaseDao.loadEvents();
}
#NonNull
#Override
protected LiveData<ApiResponse<TimelineResponse>> createCall() {
return apiService.retrieveTimeline();
}
}.getAsLiveData();
}
}
I want to use the test to check if the mocked observer is called multiple times with different values. However, the test says that it is only called one with the loading argument.
After some debugging it seems like the NetworkBoundResource's https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/repository/NetworkBoundResource.java#L48 observer registered in the constructor is not called.
Has anyone faced this issue?
I'm trying to write some tests for fragments which have fields annotated with #Inject. For example, a chunk of my app looks like this:
Module:
#Module
public class PdfFactoryModule {
#Provides #Singleton
PdfFactory providePdfFactory() {
return PdfFactory.getPdfFactory();
}
}
Component:
#Singleton
#Component(modules = PdfFactoryModule.class)
public interface CorePdfComponent {
void inject(PagerFragment pagerFragment);
}
Application:
public class CorePdfApplication extends Application {
#NonNull
private CorePdfComponent component;
#Override
public void onCreate() {
super.onCreate();
component = DaggerCorePdfComponent.builder().build();
}
#NonNull
public CorePdfComponent getComponent() {
return component;
}
}
PagerFragment:
public class PagerFragment extends Fragment {
#Inject PdfFactory pdfFactory;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Dagger 2
((CorePdfApplication) getActivity().getApplication()).getComponent().inject(this);
}
(Note that these are only snippets of my whole code, I'm showing only the essentials for this particular dependency to keep it clear.)
I was trying to do a test like this:
Fake Module:
#Module
public class FakePdfFactoryModule extends PdfFactoryModule {
#Override
PdfFactory providePdfFactory() {
return Mockito.mock(PdfFactory.class);
}
}
Fake Component:
#Singleton
#Component(modules = FakePdfFactoryModule.class)
public interface FakeCorePdfComponent extends CorePdfComponent {
void inject(PagerFragmentTest pagerFragmentTest);
}
Fake Application:
public class FakeCorePdfApplication extends CorePdfApplication {
#NonNull
private FakeCorePdfComponent component;
#Override
public void onCreate() {
super.onCreate();
component = DaggerFakeCorePdfComponent.builder().build();
}
#NonNull
public FakeCorePdfComponent getComponent() {
return component;
}
}
Test:
#RunWith(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class, sdk = 21, application = FakeCorePdfApplication.class)
public class PagerFragmentTest {
PagerFragment pagerFragment;
#Before
public void setup() {
pagerFragment = new PagerFragment();
startVisibleFragment(pagerFragment);
}
#Test
public void exists() throws Exception {
assertNotNull(pagerFragment);
}
But the DaggerFakeCorePdfComponent doesn't generate. I may have messed up big time because I never tested with dependency injection. What am I doing wrong?
My advice - "Do not use dagger in tests".
Just change your code to next:
public class FakeCorePdfApplication extends CorePdfApplication {
#NonNull
private CorePdfComponent component = mock(CorePdfComponent.class);
#Override
public void onCreate() {
super.onCreate();
}
#NonNull
public CorePdfComponent getComponent() {
return component;
}
}
And:
#RunWith(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class, sdk = 21, application = FakeCorePdfApplication.class)
public class PagerFragmentTest {
PagerFragment pagerFragment;
#Before
public void setup() {
pagerFragment = new PagerFragment();
CorePdfComponent component = ((CorePdfApplication)RuntimeEnvironment.application).getComponent();
doAnswer( new Answer() {
Object answer(InvocationOnMock invocation) {
fragment. pdfFactory = mock(PdfFactory.class);
return null;
}
}).when(component).inject(pageFragment);
startVisibleFragment(pagerFragment);
}
#Test
public void exists() throws Exception {
assertNotNull(pagerFragment);
}
}
You may try:
androidTestApt "com.google.dagger:dagger-compiler:<version>"
I was having similar problem, it worked for me.