コンストラクタでContext
を受け取って、Context.openFileInput
やContext.openFileOutput
を利用するクラスのテストコードの書き方にハマったので書いておきます。
テスト対象のクラス
public class BooleanRepository {
static final String FILE_NAME = "BOOLEAN.txt";
@NonNull private final Context context;
public BooleanRepository(@NonNull Context context) { this.context = context; }
public boolean load() throws IOException {
try (final InputStream is = context.openFileInput(FILE_NAME);
final InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
final BufferedReader reader = new BufferedReader(isr)) {
return Boolean.valueOf(reader.readLine());
}
}
public void save(boolean bool) throws IOException {
try (final OutputStream os = context.openFileOutput(FILE_NAME, Context.MODE_PRIVATE);
final OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8);
final PrintWriter writer = new PrintWriter(osw)) {
writer.append(bool);
}
}
}
結論 Robolectricを使う
Robolectricを使うことで、Androidに依存するテストもJVM上で実行することができます。
android {
compileSdkVersion 28
...
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
dependencies {
testImplementation 'androidx.test:core:1.2.0'
testImplementation 'com.google.truth:truth:0.45'
}
@RunWith(RobolectricTestRunner.class)
public class MemoRepositorySpec {
private static final boolean INPUT_BOOL = true;
private static final String INPUT_STRING = String.valueOf(INPUT_BOOL);
private BooleanRepository booleanRepository;
private Context context;
@Before
public void setUp() {
this.context = ApplicationProvider.getApplicationContext();
this.booleanRepository = new BooleanRepository(context);
}
@Test
public void load() throws Exception {
final File file = new File(context.getFilesDir(), BooleanRepository.FILE_NAME);
try (final Writer fileWriter = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) {
fileWriter.write(INPUT_STRING);
}
final boolean output = booleanRepository.load();
assertThat(output).isEqualTo(INPUT_BOOL);
}
@Test
public void save() throws Exception {
booleanRepository.save(true);
final File file = new File(context.getFilesDir(), BooleanRepository.FILE_NAME);
try (final FileInputStream fileInputStream = new FileInputStream(file)) {
final byte[] readBuffer = new byte[INPUT_STRING.length()];
fileInputStream.read(readBuffer);
assertThat(readBuffer).isEqualTo(INPUT_STRING);
}
}
}
以下蛇足
試したこと1 BufferedReader
とPrintWriter
をモックする
最初にメソッド内でnewしているBufferedReader
とPrintWriter
をPowerMock
でモックすれば良いのでは?と考えた。
しかし、PowerMock
の使い方がわからずうまく置き換わらず断念。
試したこと2 Context.openFileInput
やContext.openFileOutput
をモックする
次にContext.openFileInput
やContext.openFileOutput
がByteArrayInputStream
、ByteArrayOutputStream
を返すようにモックできれば良いのでは?と考えた。
しかしContext.openFileInput
やContext.openFileOutput
の戻り値はそれぞれFileInputStream
、FileOutputStream
なので戻り値が合わないので無理。
試したこと3 InputStream
、OutputStream
を返すメソッドを作り、それをモックする
InputStream
、OutputStream
を返すメソッドを作って、
InputStream getInputStream() throws FileNotFoundException {
return context.openFileInput(FILE_NAME);
}
OutputStream getOutputStream() throws FileNotFoundException {
return context.openFileOutput(FILE_NAME, Context.MODE_PRIVATE);
}
テスト対象のメソッドをそこから読み取るように変更する。
try (final InputStream is = getInputStream();
/* ... */
try (final OutputStream os = getOutputStream();
そのメソッドをMockito
でモックし、それぞれByteArrayInputStream
、ByteArrayOutputStream
を返すようにする。
ByteArrayInputStream
にはファイルの内容のbyte配列をコンストラクタに渡せる。
ByteArrayOutputStream
なら.toByteArray()
すると出力内容をbyte[]で読み取れる。
public class MemoRepositorySpec {
private static final boolean INPUT_BOOL = true;
private static final byte[] INPUT_BYTES = String.valueOf(INPUT_BOOL).getBypes(StandardCharsets.UTF_8);
@Spy
private BooleanRepository booleanRepository;
private Context context;
@Before
public void setUp() {
context = mock(Context.class);
booleanRepository = new BooleanRepository(context);
MockitoAnnotations.initMocks(this);
}
@Test
public void load() throws Exception {
final ByteArrayInputStream is = new ByteArrayInputStream(INPUT_BYTES);
doReturn(is).when(booleanRepository).getInputStream();
final boolean output = booleanRepository.load();
assertThat(output).isEqualTo(INPUT_BOOL);
}
@Test
public void save() throws Exception {
final ByteArrayOutputStream os = new ByteArrayOutputStream();
doReturn(os).when(booleanRepository).getOutputStream();
booleanRepository.save(true);
assertThat(os.toByteArray()).containsExactly(INPUT_BYTES);
}
}