How to test a simple setter method - android

the class i want to test is posted below in the code section. I am trying to test the "setSubscriberName" method.
the test I coded is posted below in the testing section. but at run time the test fails
please let me know how to test that setter method correctly
code
public class ListViewModel {
private String mSubscriberName = null;
public ListViewModel(String subscriberName) {
mSubscriberName = subscriberName;
}
public void setSubscriberName(String name) {
mSubscriberName = name;
}
}
testing:
public class ListViewModelTest {
#Mock
private ListViewModel mListViewModel = null;
#Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
#Before
public void setUp() throws Exception {
mListViewModel = new ListViewModel("");
}
public void setSubscriberName(String str) {
String mSubscriberName = null;
mSubscriberName = str;
}
#Test
public void setSubscriberNameTest() throws Exception {
ListViewModel spyListView = spy(mListViewModel);
verify(spyListView).setSubscriberName("abc");
}
}

Related

Mock class that extends LiveData

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);
}
}

Local unit testing with Mockito in Android studio

Can someone please show me a working unit test om this code, using mockito? Im new to testing in Android studio and could really need some help.
public class PreferenceHelper {
public static final String SHARED_PREFS_NAME = "EDUBACK_PREFS";
public static final String PREF_KEY_IS_STUDENT = "PREF_KEY_IS_STUDENT";
private final SharedPreferences mPref;
public PreferenceHelper(Context context) {
mPref = context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
}
public void setIsStudent(boolean isStudent) {
mPref.edit().putBoolean(PREF_KEY_IS_STUDENT, isStudent).apply();
}
public boolean getIsStudent() {
return mPref.getBoolean(PREF_KEY_IS_STUDENT, true); // Default true
}}
Actually it is not necessary to use mockito here.
You can test your class something like that (for assertions I use org.assertj:assertj-core:3.5.2 library):
#RunWith(RobolectricTestRunner.class)
public class PreferenceHelperTest {
private SharedPreferences sharedPreferences;
private PreferenceHelper preferenceHelper;
#Before
public void setUp() {
sharedPreferences = RuntimeEnvironment.application.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
preferenceHelper = new PreferenceHelper(ShadowApplication.getInstance().getApplicationContext());
}
#Test
public void setIsStudent_whenIsStudentIsTrue() {
preferenceHelper.setIsStudent(true);
assertThat(sharedPreferences.getBoolean(PREF_KEY_IS_STUDENT, false)).isTrue();
}
#Test
public void setIsStudent_whenIsStudentIsFalse() {
preferenceHelper.setIsStudent(false);
assertThat(sharedPreferences.getBoolean(PREF_KEY_IS_STUDENT, true)).isFalse();
}
#Test
public void getIsStudent_whenIsStudentIsNull() {
boolean getIsStudent = preferenceHelper.getIsStudent();
assertThat(getIsStudent).isTrue();
}
#Test
public void getIsStudent_whenIsStudentIsFalse() {
preferenceHelper.setIsStudent(false);
boolean getIsStudent = preferenceHelper.getIsStudent();
assertThat(getIsStudent).isFalse();
}
#Test
public void getIsStudent_whenIsStudentIsTrue() {
preferenceHelper.setIsStudent(true);
boolean getIsStudent = preferenceHelper.getIsStudent();
assertThat(getIsStudent).isTrue();
}
}

Unittesting when using Android Annotations, Mockito and MVP pattern

I've up until yesterday successfully put together a very readable Android project using the MVP-pattern and the Android Annotations library.
But yesterday when I started writing unittest for my LoginPresenter a problem has shown itself.
First some code from my LoginPresenter.
...
#EBean
public class LoginPresenterImpl implements LoginPresenter, LoginInteractor.OnLoginFinishedListener {
#RootContext
protected LoginActivity loginView;
#Bean(LoginInteractorImpl.class)
LoginInteractor loginInteractor;
#Override public void validateCredentials(String username, String password) {
if (loginView != null) {
loginView.showProgress();
}
if (TextUtils.isEmpty(username)) {
// Check that username isn't empty
onUsernameError();
}
if (TextUtils.isEmpty(password)){
// Check that password isn't empty
onPasswordError();
// No reason to continue to do login
} else {
}
}
#UiThread(propagation = UiThread.Propagation.REUSE)
#Override public void onUsernameError() {
if (loginView != null) {
loginView.setUsernameError();
loginView.hideProgress();
}
}
...
My test:
#RunWith(MockitoJUnitRunner.class)
public class LoginPresenterImplTest {
private LoginPresenter loginPresenter;
#Mock
private LoginPresenter.View loginView;
#Before
public void setUp() {
// mock or create a Context object
Context context = new MockContext();
loginPresenter = LoginPresenterImpl_.getInstance_(context);
MockitoAnnotations.initMocks(this);
}
#After
public void tearDown() throws Exception {
loginPresenter = null;
}
#Test
public void whenUserNameIsEmptyShowUsernameError() throws Exception {
loginPresenter.validateCredentials("", "testtest");
// verify(loginPresenter).onUsernameError();
verify(loginView).setUsernameError();
}
}
The problem is I've not used the standard approach of using MVP-pattern but instead trying out Android Annotations to make the code more readable. So I've not used attachView()- or detachView()-methods for attaching my presenter to my LoginActivity (view). This means that I can't mock my "view". Does someone know a workaround for this problem. I keep getting following message when running the test:
Wanted but not invoked:
loginView.setUsernameError();
-> at com.conhea.smartgfr.login.LoginPresenterImplTest.whenUserNameIsEmptyShowUsernameError(LoginPresenterImplTest.java:48)
Actually, there were zero interactions with this mock.
Solution (I'm not using #RootContext anymore):
Presenter:
#EBean
public class LoginPresenterImpl extends AbstractPresenter<LoginPresenter.View>
implements LoginPresenter, LoginInteractor.OnLoginFinishedListener {
private static final String TAG = LoginPresenterImpl.class.getSimpleName();
#StringRes(R.string.activity_login_authenticating)
String mAuthenticatingString;
#StringRes(R.string.activity_login_aborting)
String mAbortingString;
#StringRes(R.string.activity_login_invalid_login)
String mInvalidCredentialsString;
#StringRes(R.string.activity_login_aborted)
String mAbortedString;
#Inject
LoginInteractor mLoginInteractor;
#Override
protected void initializeDagger() {
Log.d(TAG, "Initializing Dagger injection");
Log.d(TAG, "Application is :" + getApp().getClass().getSimpleName());
Log.d(TAG, "Component is: " + getApp().getComponent().getClass().getSimpleName());
Log.d(TAG, "UserRepo is: " + getApp().getComponent().userRepository().toString());
mLoginInteractor = getApp().getComponent().loginInteractor();
Log.d(TAG, "LoginInteractor is: " + mLoginInteractor.getClass().getSimpleName());
}
#Override
public void validateCredentials(String username, String password) {
boolean error = false;
if (!isConnected()) {
noNetworkFailure();
error = true;
}
if (TextUtils.isEmpty(username.trim())) {
// Check that username isn't empty
onUsernameError();
error = true;
}
if (TextUtils.isEmpty(password.trim())) {
// Check that password isn't empty
onPasswordError();
error = true;
}
if (!error) {
getView().showProgress(mAuthenticatingString);
mLoginInteractor.login(username, password, this);
}
}
...
My tests (some of them):
#RunWith(AppRobolectricRunner.class)
#Config(constants = BuildConfig.class)
public class LoginPresenterImplTest {
#Rule
public MockitoRule mMockitoRule = MockitoJUnit.rule();
private LoginPresenterImpl_ mLoginPresenter;
#Mock
private LoginPresenter.View mLoginViewMock;
#Mock
private LoginInteractor mLoginInteractorMock;
#Captor
private ArgumentCaptor<LoginInteractor.OnLoginFinishedListener> mCaptor;
#Before
public void setUp() {
mLoginPresenter = LoginPresenterImpl_.getInstance_(RuntimeEnvironment.application);
mLoginPresenter.attachView(mLoginViewMock);
mLoginPresenter.mLoginInteractor = mLoginInteractorMock;
}
#After
public void tearDown() throws Exception {
mLoginPresenter.detachView();
mLoginPresenter = null;
}
#Test
public void whenUsernameAndPasswordIsValid_shouldLogin() throws Exception {
String authToken = "Success";
mLoginPresenter.validateCredentials("test", "testtest");
verify(mLoginInteractorMock, times(1)).login(
anyString(),
anyString(),
mCaptor.capture());
mCaptor.getValue().onSuccess(authToken);
verify(mLoginViewMock, times(1)).loginSuccess(authToken);
verify(mLoginViewMock, times(1)).hideProgress();
}
#Test
public void whenUsernameIsEmpty_shouldShowUsernameError() throws Exception {
mLoginPresenter.validateCredentials("", "testtest");
verify(mLoginViewMock, times(1)).setUsernameError();
verify(mLoginViewMock, never()).setPasswordError();
verify(mLoginViewMock, never()).hideProgress();
}
...
As a workaround you can have this:
public class LoginPresenterImpl ... {
...
#VisibleForTesting
public void setLoginPresenter(LoginPresenter.View loginView) {
this.loginView = loginView;
}
}
In test class:
#Before
public void setUp() {
...
MockitoAnnotations.initMocks(this);
loginPresenter.setLoginPresenter(loginView);
}
But, as a rule of thumb, when you see #VisibleForTesting annotation, that means you have ill architecture. Better to refactor your project.
Heads up to Developers that want to use Android Annotations in their project. Watch out when writing unittests that your code doesn't access the Android APIs. The underlying implementation of Android Annotations is heavily dependent on the Android APIs. So the code that is autogenerated could be dependent on this and make it difficult to write unittests.
Always remember that Android Annotations replaces your class with a final class that has an _ added at the end of it's classname. In this generated class a lot of boilerplate code is autogenerated depending on how the original class is annotated. In my case the problem is that I'm working on an Android-project and want a lot of my methods from my presenter to run on the UI-thread. This is achieved using Android Annotations using the #UIThread annotation. But this means that my method is actually wrapped with another method that calls the super-class:
#Override
public void onUsernameError() {
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
LoginPresenterImpl_.super.onUsernameError();
return;
}
UiThreadExecutor.runTask("", new Runnable() {
#Override
public void run() {
LoginPresenterImpl_.super.onUsernameError();
}
}
, 0L);
}
My testcase can't get past the line:
...
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
...
And that is of course because we don't have access to the Android APIs in a simple unittest. So in there lies the problem.
Conclusion: You have to be very careful when writing unittests for projects using Android Annotations, that the code that is autogenerated doesn't rely on Android related APIs.
It's the same problem when using androids TextUtil-class.

RoboGuice with Standard Android JUnit test cases

I want to use RoboGuice in a standard Android JUnit instrumentation test case and override one piece of my app's actual wiring with a mock for testing. I can't find anything online that explains how to do this as all of my search results go to Robolectric with RoboGuoice. I am not using Robolectric nor can I use it in my app for various reasons. Has anyone wired an app with RoboGuice and injected mocks for standard Android Intrumentation test cases?
I'm using the Roboguice 3 and I solved this problem with the following setup and teardown methods within the standard ActivityInstrumentationTestCase2.
Obviously you would need to replace new TestModule() in the snippet below with your own test module class.
#Override
protected void setUp() throws Exception {
super.setUp();
Application app = (Application)getInstrumentation().getTargetContext()
.getApplicationContext();
RoboGuice.getOrCreateBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE,
Modules.override(RoboGuice.newDefaultRoboModule(app))
.with(new TestModule()));
getActivity();
}
#Override
protected void tearDown() throws Exception {
RoboGuice.Util.reset();
super.tearDown();
}
I've managed to get it work in a simple usage way, you just bind dependencies inside rule using builder and may forget about them later, it will do everything by itself. You may think it's over engineered, but it's realy good for reusing if tyou have a many test classes with robo guice dependencies inside.
Usage in test classes looks like:
#Rule
public InjectWithMocksRule injectWithMocksRule = new InjectWithMocksRule(
this,
() -> new InjectRule
.BindingBuilder()
.add(MyClass.class, mockedClassImpl)
.add(SomeInterface.class, mockedInterfaceImpl));
I wrote helper class TestBindingModule:
public class TestBindingModule extends AbstractModule {
private HashMap<Class<?>, Object> bindings = new HashMap<Class<?>, Object>();
#Override
#SuppressWarnings("unchecked")
protected void configure() {
Set<Entry<Class<?>, Object>> entries = bindings.entrySet();
for (Entry<Class<?>, Object> entry : entries) {
bind((Class<Object>) entry.getKey()).toInstance(entry.getValue());
}
}
public void addBinding(Class<?> type, Object object) {
bindings.put(type, object);
}
public void addBindings(HashMap<Class<?>, Object> bindings) {
this.bindings.putAll(bindings);
}
public static void setUp(Object testObject, TestBindingModule module) {
Module roboGuiceModule = RoboGuice.newDefaultRoboModule(RuntimeEnvironment.application);
Module testModule = Modules.override(roboGuiceModule).with(module);
RoboGuice.getOrCreateBaseApplicationInjector(RuntimeEnvironment.application, RoboGuice.DEFAULT_STAGE, testModule);
RoboInjector injector = RoboGuice.getInjector(RuntimeEnvironment.application);
injector.injectMembers(testObject);
}
public static void tearDown() {
Application app = RuntimeEnvironment.application;
DefaultRoboModule defaultModule = RoboGuice.newDefaultRoboModule(app);
RoboGuice.getOrCreateBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE, defaultModule);
}
}
Than I use custom Rule to make it work easy:
public class InjectRule implements TestRule {
public interface BindingBuilderFactory {
BindingBuilder create();
}
public static class BindingBuilder {
private HashMap<Class<?>, Object> bindings = new HashMap<>();
public BindingBuilder add(Class<?> dependencyClass, Object implementation) {
bindings.put(dependencyClass, implementation);
return this;
}
HashMap<Class<?>, Object> buildBindings() {
return this.bindings;
}
}
private Object target;
private BindingBuilderFactory bindingBuilderFactory;
public InjectRule(Object target, BindingBuilderFactory bindingBuilderFactory) {
this.target = target;
this.bindingBuilderFactory = bindingBuilderFactory;
}
private void overrideTestInjections(Object target) {
TestBindingModule module = new TestBindingModule();
module.addBindings(this.bindingBuilderFactory.create().buildBindings());
TestBindingModule.setUp(target, module);
}
#Override
public Statement apply(Statement base, Description description) {
return new StatementDecorator(base);
}
private class StatementDecorator extends Statement {
private Statement baseStatement;
StatementDecorator(Statement b) {
baseStatement = b;
}
#Override
public void evaluate() throws Throwable {
before();
try {
baseStatement.evaluate();
} catch (Error e) {
throw e;
} finally {
after();
}
}
void after() {
TestBindingModule.tearDown();
}
void before() {
overrideTestInjections(target);
}
}
}
Also you may want to init mocks with #Mock annotation inside of your test classes, so you need another custom rule:
public class MockitoInitializerRule implements TestRule {
private Object target;
public MockitoInitializerRule(Object target) {
this.target = target;
}
#Override
public Statement apply(Statement base, Description description) {
return new MockitoInitializationStatement(base, target);
}
private class MockitoInitializationStatement extends Statement {
private final Statement base;
private Object test;
MockitoInitializationStatement(Statement base, Object test) {
this.base = base;
this.test = test;
}
#Override
public void evaluate() throws Throwable {
MockitoAnnotations.initMocks(test);
base.evaluate();
}
}
}
And, finaly, you want to combine them to mock mocks first and then set them as dependencies:
public class InjectWithMocksRule implements TestRule {
private final RuleChain delegate;
public InjectWithMocksRule(Object target, InjectRule.BindingBuilderFactory bindingBuilderFactory) {
delegate = RuleChain
.outerRule(new MockitoInitializerRule(target))
.around(new InjectRule(target, bindingBuilderFactory));
}
#Override
public Statement apply(Statement base, Description description) {
return delegate.apply(base, description);
}
}

dagger simple example gives IllegalStateException

I'm experimenting with Dagger. Now I don't fully understand how everything works.
So I wrote a test project.
This is my MainActivity:
public class MainActivity extends Activity {
private ObjectGraph mActivityGraph;
#Inject Vehicle car;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mActivityGraph = ObjectGraph.create(new ActivityModule());
mActivityGraph.validate();
mActivityGraph.inject(this);
}
}
This is my ActivityModule:
#Module(
injects =
{
Vehicle.class,
MainActivity.class,
Wheels.class
}
)
public class ActivityModule extends Application{
#Provides Wheels provideWheels()
{
return new Wheels(4,"X12");
}
}
In my manifest I added the ActivityModule as name at the application.
This is my Vehicle class:
#Module(
includes = Wheels.class
)
public class Vehicle {
#Inject
Wheels wheels;
private String type;
public Vehicle(String type) {
this.type = type;
}
}
and this is my wheels:
public class Wheels {
private int inch;
private String brand;
#Inject
public Wheels(int inch, String brand) {
this.inch = inch;
this.brand = brand;
}
}
Now what I want to accomplish is that I have a car in MainActivity that injects his wheels. Now I don't know how to create my car in the mainActivity because I want to create a car with a parameter as String on what the user fills in.
I get this:
Error:(8, 8) error: Graph validation failed: No #Module on edu.ida.daggertest.app.Wheels
Error:(20, 8) error: Unknown error java.lang.IllegalStateException thrown by javac in graph validation: Unable to create binding for edu.ida.daggertest.app.Vehicle
I found the problem. This is my edited code. The problem was the creation off the graph.
public class MainActivity extends Activity {
final static String TAG = MainActivity.class.getName();
private ObjectGraph mActivityGraph;
#Inject
Vehicle car;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mActivityGraph = ObjectGraph.create(new ActivityModule());
mActivityGraph.inject(this);
Log.d(TAG,String.valueOf(car.getWheels().getInch()));
Log.d(TAG,car.getWheels().getBrand());
}
}
#Module(
injects =
{
Vehicle.class,
MainActivity.class,
Wheels.class
},
complete = false
)
public class ActivityModule{
#Provides Wheels provideWheels()
{
return new Wheels(4,"X12");
}
}
public class Vehicle {
#Inject
Wheels wheels;
private String type;
public String getType() {
return type;
}
public Wheels getWheels() {
return wheels;
}
}
public class Wheels {
private int inch;
private String brand;
#Inject
public Wheels(int inch, String brand) {
this.inch = inch;
this.brand = brand;
}
public int getInch() {
return inch;
}
public String getBrand() {
return brand;
}
}
The completes false is required otherwise dagger is going to complain about injectable constructors Integer and String.
#Module(
includes = Wheels.class
)
public class Vehicle {
Actually this caused your compile-time error, because Wheels is not a module

Categories

Resources