Sunday, March 22, 2009

Designing Java Mock Objects with Commons-VFS

Unit tests and mock objects can get messy when dealing with file objects, but recently I have had the chance to throw in another tool into the old Java toolbox. Commons-VFS allows developers to create a virtual file system, where the files spanning multiple drives/formats/protocols can be made to appear as a single drive accessible through the VFSManager.

Common-VFS allows the developers to span the following file systems and formats...
Commons-VFS is a very useful library with many possible applications. In the following case, I would like to demonstrate using a commons-VFS FileObject in a mock object to simulate a basic runtime file manipulation. This is a bit more realistic than creating a mock object or fakes with no true file changes and is simple and easy to use while only making a virtual file updates in memory.

First, I will create an interface named PACFileHandler. The interface will define the contract for any implementation of the PACFileHandler.








/**
*
*/
package com.pac.ps.file;
/**
* @author robert
*
*/

public interface PACFileHandler {

public void open(String fileName);

public StringBuffer read();

public boolean write(StringBuffer sbf) ;

public boolean append(StringBuffer sbf);

public void close(String fileName);


}


Next, you can create an implementation of this class for runtime. The implementation is not important at this point since we are reviewing commons-VFS with mock objects.

The mock class of the implementation will implement PACFileHandler and will look like this...

/**
*
*/
package com.pac.ps.file;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;

import org.apache.commons.vfs.FileContent;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.FileSystemManager;
import org.apache.commons.vfs.FileUtil;
import org.apache.commons.vfs.VFS;

/**
* @author robert
*
*/
public class PACFileHandlerMock implements PACFileHandler {

FileObject jarFile ;

/* (non-Javadoc)
* @see com.pac.ps.file.PACFileHandler#append(java.lang.StringBuffer)
*/
public boolean append(StringBuffer sbf) {
// TODO Auto-generated method stub
return false;
}

/* (non-Javadoc)
* @see com.pac.ps.file.PACFileHandler#open(java.lang.String)
*/
public void open(String fileName) {
try {
FileSystemManager fsManager = VFS.getManager();
String fileString = "ram://" + fileName;
jarFile = fsManager.resolveFile(fileString);
} catch (FileSystemException fse) {
System.out.println("ERROR: " + fse.toString());
}
}

/* (non-Javadoc)
* @see com.pac.ps.file.PACFileHandler#read()
*/
public StringBuffer read() {
StringBuffer fileContents = new StringBuffer() ;

try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
FileUtil.writeContent(jarFile, outputStream);
fileContents.append(outputStream);
} catch (IOException fse) {
System.out.println("ERROR: " + fse);
}

return fileContents ;
}

/* (non-Javadoc)
* @see com.pac.ps.file.PACFileHandler#write(java.lang.StringBuffer)
*/
public boolean write(StringBuffer sbf) {
try {
jarFile.createFile() ;
FileContent fc = jarFile.getContent() ;
OutputStream outputStream = fc.getOutputStream() ;
PrintWriter bw = new PrintWriter(outputStream);
bw.write(sbf.toString());
bw.close();
outputStream.close();
} catch (IOException fse) {
System.out.println("ERROR: " + fse);
}

return true;
}

public void close(String fileName) {
try {
jarFile.close() ;
} catch (FileSystemException fse){
System.out.println("ERROR: " + fse);
}

}

}


The following code from the mock open() method will initialize the commons-VFS File System Manager. Notice, how the file name is referenced. The path to our virtual file will start with "ram://". Other tests could easily be created using the formats discussed earlier, but in this case we will be creating the virtual file in the ram:// path in memory.

FileSystemManager fsManager = VFS.getManager();
String fileString = "ram://" + fileName;
jarFile = fsManager.resolveFile(fileString);


The above mock class will be passed into a PACFile object which will read and write the contents of the file using the PACFileHandlerMock class. In runtime the PACFile object will reference the PACFileHandlerImpl, and our mock object should imitate a true File object without many differences.

/**
*
*/
package com.pac.ps.file;

/**
* @author robert
*
*/
public class PACFile {

PACFileHandler pfh ;

public PACFile(PACFileHandler pfHandler){
this.pfh = pfHandler ;
}

public boolean write(String fileName,String contents){

pfh.open(fileName);
StringBuffer line = new StringBuffer(contents);
pfh.write(line);
pfh.close(fileName);

return true ;
}

public StringBuffer read(String fileName){

StringBuffer line = pfh.read() ;

return line;
}
}

Last, let's create a JUnit 4 test case called PACFileTest...

/**
*
*/
package com.pac.ps.file;

import org.junit.Test;
import junit.framework.TestCase;

/**
* @author robert
*
*/
public class PACFileTest extends TestCase {

/**
* Test method for {@link com.pac.ps.file.PACFile#PACFile(com.pac.ps.file.PACFileHandler)}.
*/
@Test
public void testPACFile() {
PACFileHandlerMock pfhm = new PACFileHandlerMock() ;
PACFile pacFile = new PACFile(pfhm) ;

String fileName = "testFile";
pacFile.write(fileName,"Four score and seven years ago");
StringBuffer sb = pacFile.read(fileName);

assertEquals("Four score and seven years ago",sb.toString());

}


}

Next run the previous unit test...

Output:
Mar 25, 2009 11:45:58 PM org.apache.commons.vfs.VfsLog info
INFO: Using "/tmp/vfs_cache" as temporary files store.


The previous code will create the mock PACFileHandler, and after create and write a virtual file to memory with the contents "Four score and seven years ago".

The file will then be read back and regurgitated back into the unit test assertion for a final check. Many permutations of this example can be made for test cases. Many of the commons-VFS library formats outside of ram:// can be used instead (ftp, http, smb, etc.)

1 comment:

Anonymous said...

Many thanks for this comprehensive example.