9

I have a heavily dependency injected (dagger2) application. I would like to run an espresso test without having the test navigate through the whole application, and log into the application.

I would like to start on my teleActivity, and mock the login manager. However in any @test function, we have already hit the null pointer as we have called onCreate. If I override it before we launch the activity (show below) the activity is null.

To my understanding, the ability to switch our underlining dependencies is a large reason why we use Dagger2, else it would be just a very over engineered singleton. How do I override, mock, or switch the injection to a testing dagger module -- so I can create this simple espresso test.

Note I also wrote all this in the MVP design pattern if that makes a difference.

TeleActivity

@Inject
TelePresenter mTelePresenter;
@Inject
public LoginStateManager mLoginStateManager;

    @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ButterKnife.bind(this);
    DaggerInjectorTele.get().inject(this);
    mTelePresenter.setTeleDependencies(this);
    Intent intent = getIntent();

    String searchId = null;

    if (intent != null) {
        searchId = intent.getStringExtra(Constants.SEARCH_ID);
       }

    mTelePresenter.onCreateEvent(searchId,
            Helper.makeAuthorizationHeader(
            // CRASH Null pointer
            mLoginStateManager.getBaseLoginResponse().getAccessToken()));

}

Espresso

@LargeTest
@RunWith(AndroidJUnit4.class)
public class TeleTest {
    @Rule
    public ActivityTestRule<TeleActivity> mActivityTestRule = new ActivityTestRule(
            TeleActivity.class) {
        @Override
        protected void beforeActivityLaunched() {
            super.beforeActivityLaunched();
            TeleActivity teleActivity = (TeleActivity)getActivity();
             //teleActivity NULL!
            teleActivity.mLoginStateManager = mock(LoginStateManager.class);
            LoginResponse loginResponse = mock(LoginResponse.class);
            when(loginResponse.getAccessToken()).thenReturn("1234");
            // Nope here still null


when(teleActivity.mLoginStateManager.getBaseLoginResponse()).thenReturn(loginResponse);

        }
    };

Dagger Injector

  public class DaggerInjectorTele {
    private static TelePresenterComponent telePresenterComponent =
            DaggerTelePresenterComponent.builder().build();

    public static TelePresenterComponent get() {
        return telePresenterComponent;
    }
}

TelePresenterComponent

@Singleton
@Component(modules = {TelePresenterModule.class,
        LoginStateManagerModule.class})
public interface TelePresenterComponent {
    void inject(TeleActivity activity);
}

TelePresenterModule

@Module
public class TelePresenterModule {

    @Provides
    @Singleton
    public TelePresenter getTelePresenter() {
        return new TelePresenter();
    }
}

LoginStateManagerModule

@Module
public class LoginStateManagerModule {

    @Provides
    @Singleton
    public LoginStateManager getLoginStateManager(){
        return new LoginStateManager();
    }
}
StarWind0
  • 1,554
  • 2
  • 17
  • 46

3 Answers3

8

First, your decision to use dependency injection (Dagger2) is a very good one and will indeed make your tests easier to write.

You have to override dependency injection configuration (module) and inject a mock. Here is a simple example of how it can be done.

First you need a mock:

LoginStateManager lsmMock = mock(LoginStateManager.class);

Now override the DI config to use this mock:

//Extend your TelePresenterModule, override provider method
public class TestTelePresenterModule extends TelePresenterModule{
    @Override
    public LoginStateManager getLoginStateManager() {
        //simply return the mock here
        return lsmMock;
    }
}

Now to the test:

@Test
//this is an espresso test
public void withAMock() {
    //build a new Dagger2 component using the test override
    TelePresenterComponent componentWithOverride = DaggerTelePresenterComponent.builder()
            //mind the Test in the class name, see a class above
            .telePresenterModule(new TestTelePresenterModule())
            .build();

    //now we initialize the dependency injector with this new config
    DaggerInjectorTele.set(componentWithOverride);

    mActivityRule.launchActivity(null);

    //verify that injected mock was interacted with
    verify(lsmMock).whatever();
}

Example from: https://github.com/yuriykulikov/DIComparison/blob/master/app/src/androidTest/java/com/example/yuriy/dependencyinjectioncomparison/Dagger2Test.java

Yuriy Kulikov
  • 2,059
  • 1
  • 16
  • 29
  • I was so close! The other examples I saw did not override the injector. I did one other thing, which I will post under my question. – StarWind0 Apr 04 '17 at 15:52
0

Seems like architecture problem rather than a minor issue.

First off I wouldn't create a static class for calling dagger2 component my approach would be more android centric, I mean using singleton application with all of its bells and whistles.

anyway... Best way to run testing without running whole work-flow is to separate your project into two different projects:

1- UI app, your android activities and fragments, etc...

2-Logic Module using an enterprise architecture say MVP/MVC/MVVM (it should be a different project inside your android studio)

Where should you use dagger? inside your UI app for sticking the logic module into your UI.

How can you test different part of app (Logic Module)? since you separated your logic into different part writing tests for them would be much easier even though you are not gonna need Esperesso anymore. simple unit testing wiht JUnit and Mockito can help you without running the whole work-flow.

note that you shouldn't have any kind of logic inside your UI app.

My opinion is Clean Architecture : https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

I have a simple scaffold for above approach in my github you can read that too, well if you fancy : https://github.com/vahidhashemi/android_clean_architecture

Vahid Hashemi
  • 5,182
  • 10
  • 58
  • 88
  • Hi Mahid, thank you for the answer. However I am afraid my question was not, what is the best architecture for unit tests. It is how to mock for an espresso test. I do like how you suggest I do MVP, as my question did state we use MVP architecture. Following, even with the changes you suggested, I am afraid we still have the same problem. Now I need to mock a singleton instead of an injected singleton. – StarWind0 Apr 03 '17 at 15:48
0

there is no value set in

LoginStateManager

so when you build the component you get both TelePresenter Dependency and LoginStateManager dependency but no values set in member variables of the both .So i think you need to set the values of member variables before accessing them.

getBaseLoginResponse().getAccessToken())

the above line of code is giving you null because you haven't set the value .So before accessing it you need to set the values first

Navneet Kumar
  • 273
  • 1
  • 11
  • Hi Navneet. Welcome to the group. I would advise reading the questions very carefully. You will notice that this is an Espresso, Dagger2, Mocking question. If you are not familiar with these topics, then you will not be able to answer. The fact the manager is not set, is exactly the issue we are trying to solve. The question is HOW to set these values, in this environment. – StarWind0 Apr 03 '17 at 15:38