-------------------------------------------------- StubFtpServer Getting Started -------------------------------------------------- StubFtpServer - Getting Started <<StubFtpServer>> is a "stub" implementation of an FTP server. It supports the main FTP commands by implementing command handlers for each of the corresponding low-level FTP server commands (e.g. RETR, DELE, LIST). These <CommandHandler>s can be individually configured to return custom data or reply codes, allowing simulation of a complete range of both success and failure scenarios. The <CommandHandler>s can also be interrogated to verify command invocation data such as command parameters and timestamps. <<StubFtpServer>> works out of the box with reasonable defaults, but can be fully configured programmatically or within a {{{http://www.springframework.org/}Spring Framework}} (or similar) container. Here is how to start the <<StubFtpServer>> with the default configuration. This will return success reply codes, and return empty data (for retrieved files, directory listings, etc.). +------------------------------------------------------------------------------ StubFtpServer stubFtpServer = new StubFtpServer(); stubFtpServer.start(); +------------------------------------------------------------------------------ If you are running on a Unix system, you probably need to use a different server control port, since the default port (21) is likely already in use or cannot be bound from a user process. Use the <<<StubFtpServer.setServerControlPort(int serverControlPort)>>> method to set a different port number, such as 9187. * CommandHandlers <CommandHandler>s are the heart of the <<StubFtpServer>>. <<StubFtpServer>> creates an appropriate default <CommandHandler> for each (supported) FTP server command. See the list of <CommandHandler> classes associated with FTP server commands in {{{stubftpserver-commandhandlers.html}FTP Commands and CommandHandlers}}. You can retrieve the existing <CommandHandler> defined for an FTP server command by calling the <<<StubFtpServer.getCommandHandler(String name)>>> method, passing in the FTP server command name. For example: +------------------------------------------------------------------------------ PwdCommandHandler pwdCommandHandler = (PwdCommandHandler) stubFtpServer.getCommandHandler("PWD"); +------------------------------------------------------------------------------ You can replace the existing <CommandHandler> defined for an FTP server command by calling the <<<StubFtpServer.setCommandHandler(String name, CommandHandler commandHandler)>>> method, passing in the FTP server command name, such as <<<"STOR">>> or <<<"USER">>>, and the <<<CommandHandler>>> instance. For example: +------------------------------------------------------------------------------ PwdCommandHandler pwdCommandHandler = new PwdCommandHandler(); pwdCommandHandler.setDirectory("some/dir"); stubFtpServer.setCommandHandler("PWD", pwdCommandHandler); +------------------------------------------------------------------------------ ** Generic CommandHandlers <<StubFtpServer>> includes a couple generic <CommandHandler> classes that can be used to replace the default command handler for an FTP command. See the Javadoc for more information. * <<StaticReplyCommadHandler>> <<<StaticReplyCommadHandler>>> is a <CommandHandler> that always sends back the configured reply code and text. This can be a useful replacement for a default <CommandHandler> if you want a certain FTP command to always send back an error reply code. * <<SimpleCompositeCommandHandler>> <<<SimpleCompositeCommandHandler>>> is a composite <CommandHandler> that manages an internal ordered list of <CommandHandler>s to which it delegates. Starting with the first <CommandHandler> in the list, each invocation of this composite handler will invoke (delegate to) the current internal <CommandHander>. Then it moves on the next <CommandHandler> in the internal list. * Programmatic Configuration You can customize the behavior of the FTP server through programmatic configuration. <<StubFtpServer>> automatically creates a default <CommandHandler> for each supported command. If you want to customize the behavior of the server, you should create and configure a replacement <CommandHandler> for each command to be customized. The {{{#Example}Example Test Using Stub Ftp Server}} illustrates replacing the default <CommandHandler> with a customized handler. * Spring Configuration You can easily configure a <<StubFtpServer>> instance in the {{{http://www.springframework.org/}Spring Framework}}. The following example shows a <Spring> configuration file. +------------------------------------------------------------------------------ <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> <bean id="stubFtpServer" class="org.mockftpserver.stub.StubFtpServer"> <property name="commandHandlers"> <map> <entry key="LIST"> <bean class="org.mockftpserver.stub.command.ListCommandHandler"> <property name="directoryListing"> <value> 11-09-01 12:30PM 406348 File2350.log 11-01-01 1:30PM <DIR> 0 archive </value> </property> </bean> </entry> <entry key="PWD"> <bean class="org.mockftpserver.stub.command.PwdCommandHandler"> <property name="directory" value="foo/bar" /> </bean> </entry> <entry key="DELE"> <bean class="org.mockftpserver.stub.command.DeleCommandHandler"> <property name="replyCode" value="450" /> </bean> </entry> <entry key="RETR"> <bean class="org.mockftpserver.stub.command.RetrCommandHandler"> <property name="fileContents" value="Sample file contents line 1 Line 2 Line 3"/> </bean> </entry> </map> </property> </bean> </beans> +------------------------------------------------------------------------------ This example overrides the default handlers for the following FTP commands: * LIST - replies with a predefined directory listing * PWD - replies with a predefined directory pathname * DELE - replies with an error reply code (450) * RETR - replies with predefined contents for a retrieved file [] And here is the Java code to load the above <Spring> configuration file and start the configured <<StubFtpServer>>. +------------------------------------------------------------------------------ ApplicationContext context = new ClassPathXmlApplicationContext("stubftpserver-beans.xml"); stubFtpServer = (StubFtpServer) context.getBean("stubFtpServer"); stubFtpServer.start(); +------------------------------------------------------------------------------ * Retrieving Command Invocation Data Each <CommandHandler> manages a List of <<<InvocationRecord>>> objects -- one for each time the <CommandHandler> is invoked. An <<<InvocationRecord>>> contains the <<<Command>>> that triggered the invocation (containing the command name and parameters), as well as the invocation timestamp and client host address. The <<<InvocationRecord>>> also contains a <<<Map>>>, with optional <CommandHandler>-specific data. See the Javadoc for more information. You can retrieve the <<<InvocationRecord>>> from a <CommandHandler> by calling the <<<getInvocation(int index)>>> method, passing in the (zero-based) index of the desired invocation. You can get the number of invocations for a <CommandHandler> by calling <<<numberOfInvocations()>>>. The {{{#Example}Example Test Using Stub Ftp Server}} below illustrates retrieving and interrogating an <<<InvocationRecord>>> from a <CommandHandler>. * {Example} Test Using StubFtpServer This section includes a simplified example of FTP client code to be tested, and a JUnit test for it that uses <<StubFtpServer>>. ** FTP Client Code The following <<<RemoteFile>>> class includes a <<<readFile()>>> method that retrieves a remote ASCII file and returns its contents as a String. This class uses the <<<FTPClient>>> from the {{{http://commons.apache.org/net/}Apache Commons Net}} framework. +------------------------------------------------------------------------------ public class RemoteFile { private String server; public String readFile(String filename) throws SocketException, IOException { FTPClient ftpClient = new FTPClient(); ftpClient.connect(server); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); boolean success = ftpClient.retrieveFile(filename, outputStream); ftpClient.disconnect(); if (!success) { throw new IOException("Retrieve file failed: " + filename); } return outputStream.toString(); } public void setServer(String server) { this.server = server; } // Other methods ... } +------------------------------------------------------------------------------ ** JUnit Test For FTP Client Code Using StubFtpServer The following <<<RemoteFileTest>>> class includes a couple of JUnit tests that use <<StubFtpServer>>. +------------------------------------------------------------------------------ import java.io.IOException; import org.mockftpserver.core.command.InvocationRecord; import org.mockftpserver.stub.StubFtpServer; import org.mockftpserver.stub.command.RetrCommandHandler; import org.mockftpserver.test.AbstractTest; public class RemoteFileTest extends AbstractTest { private static final String FILENAME = "dir/sample.txt"; private RemoteFile remoteFile; private StubFtpServer stubFtpServer; /** * Test readFile() method */ public void testReadFile() throws Exception { final String CONTENTS = "abcdef 1234567890"; // Replace the default RETR CommandHandler; customize returned file contents RetrCommandHandler retrCommandHandler = new RetrCommandHandler(); retrCommandHandler.setFileContents(CONTENTS); stubFtpServer.setCommandHandler("RETR", retrCommandHandler); stubFtpServer.start(); String contents = remoteFile.readFile(FILENAME); // Verify returned file contents assertEquals("contents", CONTENTS, contents); // Verify the submitted filename InvocationRecord invocationRecord = retrCommandHandler.getInvocation(0); String filename = invocationRecord.getString(RetrCommandHandler.PATHNAME_KEY); assertEquals("filename", FILENAME, filename); } /** * Test the readFile() method when the FTP transfer fails (returns a non-success reply code) */ public void testReadFileThrowsException() { // Replace the default RETR CommandHandler; return failure reply code RetrCommandHandler retrCommandHandler = new RetrCommandHandler(); retrCommandHandler.setFinalReplyCode(550); stubFtpServer.setCommandHandler("RETR", retrCommandHandler); stubFtpServer.start(); try { remoteFile.readFile(FILENAME); fail("Expected IOException"); } catch (IOException expected) { // Expected this } } /** * @see org.mockftpserver.test.AbstractTest#setUp() */ protected void setUp() throws Exception { super.setUp(); remoteFile = new RemoteFile(); remoteFile.setServer("localhost"); stubFtpServer = new StubFtpServer(); } /** * @see org.mockftpserver.test.AbstractTest#tearDown() */ protected void tearDown() throws Exception { super.tearDown(); stubFtpServer.stop(); } } +------------------------------------------------------------------------------ Things to note about the above test: * The <<<StubFtpServer>>> instance is created in the <<<setUp()>>> method, but is not started there because it must be configured differently for each test. The <<<StubFtpServer>>> instance is stopped in the <<<tearDown()>>> method, to ensure that it is stopped, even if the test fails. * FTP Command Reply Text ResourceBundle The default text asociated with each FTP command reply code is contained within the "ReplyText.properties" ResourceBundle file. You can customize these messages by providing a locale-specific ResourceBundle file on the CLASSPATH, according to the normal lookup rules of the ResourceBundle class (e.g., "ReplyText_de.properties"). Alternatively, you can completely replace the ResourceBundle file by calling the calling the <<<StubFtpServer.setReplyTextBaseName(String)>>> method.