[JAVA] Test a class that takes a Context and reads / writes a file

I'm addicted to how to write test code for a class that receives Context in the constructor and uses Context.openFileInput and Context.openFileOutput, so I'll write it down.

Class to be tested

BooleanRepository.java


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

Conclusion Use * Robolectric *

build.gradle


android {
    compileSdkVersion 28
    ...
    testOptions {
        unitTests {
            includeAndroidResources = true
        }
    }
}

dependencies {
    testImplementation 'androidx.test:core:1.2.0'
    testImplementation 'com.google.truth:truth:0.45'
}

BooleanRepositorySpec.java


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

Below

Tried 1 Mock BufferedReader and PrintWriter

Why not mock the new BufferedReader and PrintWriter in the method with PowerMock first? I thought. However, I didn't know how to use PowerMock and gave up because I couldn't replace it well.

Tried 2 Mock Context.openFileInput and Context.openFileOutput

Next, what if we could mock Context.openFileInput and Context.openFileOutput to return ByteArrayInputStream and ByteArrayOutputStream? I thought. However, the return values of Context.openFileInput and Context.openFileOutput are FileInputStream and FileOutputStream, respectively, so the return values do not match, so it is impossible.

Tried 3 Create a method that returns ʻInputStream, ʻOutputStream and mock it

Create a method that returns ʻInputStream, ʻOutputStream,

BooleanRepository.java


    InputStream getInputStream() throws FileNotFoundException {
        return context.openFileInput(FILE_NAME);
    }

    OutputStream getOutputStream() throws FileNotFoundException {
        return context.openFileOutput(FILE_NAME, Context.MODE_PRIVATE);
    }

Change the method under test to read from it.

BooleanRepository.java


        try (final InputStream is = getInputStream();
        /* ... */
        try (final OutputStream os = getOutputStream();

Mock that method with Mockito and return ByteArrayInputStream and ByteArrayOutputStream respectively. You can pass the byte array of the file contents to the constructor for ByteArrayInputStream. For ByteArrayOutputStream, you can read the output contents in byte [] by doing.toByteArray ().

BooleanRepositorySpec.java


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

Recommended Posts

Test a class that takes a Context and reads / writes a file
Create a static file that expands variables using the ERB class
How to test a private method in Java and partially mock that method
I made a class that can use JUMAN and KNP from Java
Why does Java call a file a class?
Reading a file using Java's Scanner class
Implementation that creates a temporary file, compresses it, does some processing, and deletes it
How to test a class that handles application.properties with SpringBoot (request: pointed out)