Robolectric+mockito

2013-10-15T00:00:00+00:00 Android Java robolectric

Robolectric+powermockitoでRobolectricを利用している際におけるstaticメソッドのモック化っていうのを書いたけど、単純にRobolectric+Mockitoを使ったインスタンスメソッドのモック化に関してはスルーしてたので色々検証

まぁ通常であればRobolectricでActivityをテストするのには

package shareroid.app;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;

@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class SampleActivityTestCase {

    @Test
    public void 通常テスト() throws Exception {
        SampleActivity activity = Robolectric.buildActivity(SampleActivity.class)
            .create()
            .start()
            .get();

        assertThat(activity.mTextView, notNullValue());
        assertThat(activity.mTextView.getText().toString(), is("aG9nZQ"));
    }
}

んな感じで実行するのだけど、Robolectric.buildActivityの返り値はorg.robolectric.util.ActivityControllerになる。でcreateメソッドで内部的なattachとperformCreateメソッド(継承しても不可視なメソッド)をコール、startでperformStartをコールするっていう仕組みになっている模様

まぁそこは良いとして、要点としては

  • Robolectric.buildActivityを利用するのでMockitoを使ってどうやってモック化するのか
  • onCreateが走る前にonCreateメソッドが利用する他のメソッドをモック化する場合はどうすれば良いのか
  • 上記のようにRobolectricクラスを使ってonStartまでを処理させず自分でやりたい場合はどうすれば良いのか

っていう所。1に関してはMockito.spyを使えば良いはずなので、2と3に関して色々やってみる。本題はここから

その前に対象となるActivityは

package shareroid.app;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class SampleActivity extends Activity {

    TextView mTextView;

    @Override
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        mTextView = new TextView(this);
        mTextView.setText(buildText());
    }

    @Override
    protected void onStart() {
        super.onStart();
        mTextView = new TextView(this);
        mTextView.setText(buildText());
    }

    String buildText() {
        return Sample.encodeB64("hoge");
    }
}

onCreateをRobolectric側で作用させずにモック化してから実行する場合

※onStartは使わないのでコメントアウト

まぁただTextViewなインスタンス作って、それのテキストにbuildTextを使うだけ。でそのbuildTextメソッドをモック化して返す値を変えたりするだけ

package shareroid.app;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.util.ActivityController;

import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;

@PrepareForTest(SampleActivity.class)
@PowerMockIgnore({ "android.*", "org.robolectric.*" })
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class SampleActivityTestCase {

    @Test
    public void onCreateTest() throws Exception {
        ActivityController<sampleActivity> controller = Robolectric
            .buildActivity(SampleActivity.class);

        // onCreateをあとで実行する場合には一度attachをしておかないとNPEが発生する
        SampleActivity activity = controller.attach().get();
        activity = spy(activity);

        doAnswer(
            new Answer<string>() {
                @Override
                public String answer(InvocationOnMock invocation) throws Throwable {
                    return "hoge";
                }
            }
        ).when(activity).buildText();

        activity.onCreate(null);
        assertThat(activity.mTextView, notNullValue());
        assertThat(activity.mTextView.getText().toString(), is("hoge"));

        verify(activity).buildText();
    }
}

onStartをRobolectricで作用させずにモック化する場合

package shareroid.app;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.util.ActivityController;

import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;

@PrepareForTest(SampleActivity.class)
@PowerMockIgnore({ "android.*", "org.robolectric.*" })
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class SampleActivityTestCase {

    @Test
    public void onStartTest() throws Exception {
        ActivityController<sampleActivity> controller = Robolectric
            .buildActivity(SampleActivity.class);

        SampleActivity activity = controller.create().get();
        activity = spy(activity);

        doAnswer(
            new Answer<string>() {
                @Override
                public String answer(InvocationOnMock invocation) throws Throwable {
                    return null;
                }
            }
        ).when(activity).buildText();

        activity.onStart();
        assertThat(activity.mTextView, notNullValue());
        assertThat(activity.mTextView.getText().toString(), isEmptyOrNullString());

        verify(activity).buildText();
    }
}

まぁこれに関しては特にツッコミどころ無い。ActivityController.startをしないだけで自分でonStartするだけだし。ActivityController.createはやってるけど、それもやらないのであればonCreateな時と同様に一度attachをしておく必要がある

無難なのはonCreateでモック化する必要とするようなケースの仕様にしない方がテストもすんなり出来るんじゃないかなっては思う所。onCreateでグダグダしてもねぇって所も

Mockitoでfinalクラスをモック化 (2) Robolectric+powermockito