Scala Test-Driven Development (TDD): Unit Testing File Operations with Specs2 and Mockito

In this article we'll go through the exercise of writing a method that will write string content to the specified file. There will be an option to specify whether we should overwrite an existing file. In addition, directories should be created if they do not already exist.

Programming language is Scala and testing framework that will be used is Specs2. In the spirit of unit testing, instead of interactions with the file system we'll use mocks with Mockito (already included in Specs2). All the code will be done using Test-Driven Development (TDD).

This article is based on an existing code done for the open source application BDD Assistant located in the TechnologyConversationsBdd repository.

Let's get started.

In order to use Specs2 and Mockito, we'll create a specs class that extends both. The initial class declaration is following:

[BddFileSpec.scala]

BddFile#saveFile is the holder of all specifications related to the saveFile method that we're about to write. The method itself is following:

[BddFile.scala]

At the moment it only returns true. As we progress with the exercise, it will grow to fulfill all specifications.

The first specification should verify that when file exists and should not be overwritten, method should return false.

[BddFileSpec.scala]

Important thing to note about this code is that we are mocking the File class. When classes are mocked, Mockito replaces all their public and protected methods with mocks. Real File class will not be used at all. Further on, we're stubbing the exists method so that it always returns true. In this way we're telling methods in the mocked class how to behave. Without mocks specification would run slower due to its need to interact with the file system and, more importantly, we'd need to make sure that the file does exists. In this way we are creating more reliable and faster tests. Keep in mind that we are trying to test a single unit (in this case the method saveFile and trust that all other methods or objects it interacts with are already tested and working as expected.

When this specification is run it should fail. Remember the TDD process: write one test, confirm that it fails, write the implementation, confirm that the test run is successful, refactor if needed, repeat. Small steps that lead towards the complete solution.

Here's the code that makes the above specification pass.

[BddFile.scala]

Let's move to the next specification.

[BddFileSpec.scala]

In this specification we are telling the mocked File class to return another mock declared as parentDir when method getParentFile is called. Further on, we are verifying that mkdirs was called exactly one time.

This implementation code is following.

[BddFile.scala]

Let's move to the next specification.

[BddFileSpec.scala]

In the similar fashion as the previous specification, this time we're verifying that mkdirs was not called.

Implementation code is following.

[BddFile.scala]

Off to the next specification.

[BddFileSpec.scala]

We'll use FileUtils from Apache Commons IO to write the content to the file. However, FileUtils.writeStringToFile is a static method that cannot be easily mocked with Mockito. To avoid the real method being called, we'll move it to the separate method writeStringToFile. Alternative to this approach would be to use PowerMock that has more options when compared to Mockito.

This specification introduces a concept of spies. Unlike mocks that replace all methods with fake ones, spies replace only those that we specify. In this case, we're telling the spied BddFile to do nothing when writeStringToFile is called. Later on we're verifying that the method writeStringToFile is called exactly one time.

The implementation code is following.

[BddFile.scala]

Since the introduction of the writeStringToFile method, our previous specifications will fail. To fix that, we'll need to go back and create spies in all but the first one. You'll be able to see the full code at the end of the article.

We are almost done. All that is left is to make sure that our method returns true whenever file was written. To do that, we'll write two additional specifications.

[BddFileSpec.scala]

The implementation is easy and requires us to change the last false to true.

[BddFile.scala]

That's it. We have a fully working method that writes contents of a string to a file, creates directories if required and does nothing when overwrite is set to false.

Complete specifications code can be found in BddFileSpec.scala together with the implementation in BddFile.scala. Both are part of the open source project BDD Assistant with the code located in the TechnologyConversationsBdd GitHub repository.

Leave a Reply