0

I am using AndroidAnnotations in my project and I want to test a presenter.

The test suite runs and @Test methods are apparently called before the injection has finished, because I get NullPointerException whenever I try to use the `LoginPresenter in my test code.

@RunWith(MockitoJUnitRunner.class)
@EBean
public class LoginPresenterTest {

    @Bean
    LoginPresenter loginPresenter;

    @Mock
    private LoginView loginView;

    @AfterInject
    void initLoginPresenter() {
        loginPresenter.setLoginView(loginView);
    }

    @Test
    public void whenUserNameIsEmptyShowErrorOnLoginClicked() throws Exception {
        when(loginView.getUserName()).thenReturn("");
        when(loginView.getPassword()).thenReturn("asdasd");
        loginPresenter.onLoginClicked();
        verify(loginView).setEmailFieldErrorMessage();
    }
}
Kaloyan Roussev
  • 14,515
  • 21
  • 98
  • 180

1 Answers1

2

AndroidAnnotations works by creating subclasses of the annotated classes, and adds boilerplate code in them. Then when you use your annotated classes, you will swap the generated classes in either implicitly (by injecting) or explicitly (by accessing a generated class, for example starting an annotated Activity).

So in this case to make it work, you should have run the annotation processing on the test class LoginPresenterTest, and run the test only on the generated LoginPresenterTest_ class. This can be done, but i suggest a cleaner way:

@RunWith(MockitoJUnitRunner.class)
public class LoginPresenterTest {

    private LoginPresenter loginPresenter;

    @Mock
    private LoginView loginView;

    @Before
    void setUp() {
        // mock or create a Context object
        loginPresenter = LoginPresenter_.getInstance_(context);
    }

    @Test
    public void whenUserNameIsEmptyShowErrorOnLoginClicked() throws Exception {
        when(loginView.getUserName()).thenReturn("");
        when(loginView.getPassword()).thenReturn("asdasd");
        loginPresenter.onLoginClicked();
        verify(loginView).setEmailFieldErrorMessage();
    }
}

So you have a normal test class, and you instantiate the generated bean by calling the generated factory method.

WonderCsabo
  • 11,947
  • 13
  • 63
  • 105
  • how do I gain access to a context object out there (in the LoginPresenterTest class) - sorry, its my first time doing unit testing – Kaloyan Roussev Mar 17 '16 at 15:24
  • I extended `InstrumentationTestCase` and tried `getInstrumentation().getContext()` in `getInstance_(Context context)` but I am getting a NPE on that line – Kaloyan Roussev Mar 17 '16 at 15:41
  • I suggest reading the anwers [here](http://stackoverflow.com/questions/8605611/get-context-of-test-project-in-android-junit-test-case). But you should get the context that way. Actually what object was null, the instrumentation or its context? – WonderCsabo Mar 17 '16 at 16:13
  • I tried everything, I tried those too. No context. Is there another way to wait for the injection to pass before tests are run? – Kaloyan Roussev Mar 17 '16 at 16:18
  • That context should be available. Are you sure you are running the test correctly on a device or emulator? – WonderCsabo Mar 17 '16 at 16:30
  • the only thing I can think of is that these tests are in a test folder and not an androidTest folder. Might that be the problem? – Kaloyan Roussev Mar 17 '16 at 16:31
  • These should be in the `androidTest` folder. `test` is for unit tests running on the local machine. – WonderCsabo Mar 17 '16 at 19:04
  • Although I wanted these to be unit tests, the only way I was able to get a hold of the context was to make them instrumentation tests. What you suggested worked and now I dont get a null pointer exception :) – Kaloyan Roussev Mar 18 '16 at 09:35
  • How do I use Robolectric in order to resolve the original problem in my question – Kaloyan Roussev Mar 18 '16 at 10:33
  • You can pass `Robolectric.application` as a context, or you can an `Activity` and pass that. Or you can even just mock `Context`. :) – WonderCsabo Mar 18 '16 at 10:38
  • I don't think mocking the context will do any good, because the constructor of LoginPresenter_.getInstance(context) needs this context in order to gain access to resources, string files and other real stuff. I will try the Robolectric thing. – Kaloyan Roussev Mar 18 '16 at 10:41