diff --git a/briar-core/src/net/sf/briar/plugins/modem/ModemPlugin.java b/briar-core/src/net/sf/briar/plugins/modem/ModemPlugin.java
index 8ece3df1fdfe786217dc0f144e5f406897edf729..a4643820766e61ac95c137cf1ae2d181e883e572 100644
--- a/briar-core/src/net/sf/briar/plugins/modem/ModemPlugin.java
+++ b/briar-core/src/net/sf/briar/plugins/modem/ModemPlugin.java
@@ -96,6 +96,8 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
 	private boolean resetModem() {
 		if(!running) return false;
 		for(String portName : serialPortList.getPortNames()) {
+			if(LOG.isLoggable(INFO))
+				LOG.info("Trying to initialise modem on " + portName);
 			modem = modemFactory.createModem(this, portName);
 			try {
 				if(!modem.start()) continue;
diff --git a/briar-tests/build.xml b/briar-tests/build.xml
index 1acfdcb3c0c10e13511d9eb2bbd8a4ba8227d8c9..f1171d90522c2e3f8fbfb3e3569cd35e2920eaf3 100644
--- a/briar-tests/build.xml
+++ b/briar-tests/build.xml
@@ -86,6 +86,7 @@
 			<test name='net.sf.briar.plugins.file.RemovableDrivePluginTest'/>
 			<test name='net.sf.briar.plugins.file.UnixRemovableDriveMonitorTest'/>
 			<test name='net.sf.briar.plugins.modem.CountryCodesTest'/>
+			<test name='net.sf.briar.plugins.modem.ModemPluginTest'/>
 			<test name='net.sf.briar.plugins.tcp.LanTcpPluginTest'/>
 			<test name='net.sf.briar.protocol.AckReaderTest'/>
 			<test name='net.sf.briar.protocol.BatchReaderTest'/>
diff --git a/briar-tests/src/net/sf/briar/plugins/modem/ModemPluginTest.java b/briar-tests/src/net/sf/briar/plugins/modem/ModemPluginTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..26bf721c3a2c6cb4930db998aec9733aeae5ab1c
--- /dev/null
+++ b/briar-tests/src/net/sf/briar/plugins/modem/ModemPluginTest.java
@@ -0,0 +1,287 @@
+package net.sf.briar.plugins.modem;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import net.sf.briar.BriarTestCase;
+import net.sf.briar.api.ContactId;
+import net.sf.briar.api.TransportProperties;
+import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
+import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
+
+import org.hamcrest.Description;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+import org.junit.Test;
+
+public class ModemPluginTest extends BriarTestCase {
+
+	private static final String ISO_1336 = "GB";
+	private static final String NUMBER1 = "0123";
+	private static final String NUMBER2 = "0234";
+	private static final String NUMBER3 = "0345";
+
+	@Test
+	public void testModemCreation() throws Exception {
+		Mockery context = new Mockery();
+		final ModemFactory modemFactory = context.mock(ModemFactory.class);
+		final SerialPortList serialPortList =
+				context.mock(SerialPortList.class);
+		final ModemPlugin plugin = new ModemPlugin(null, modemFactory,
+				serialPortList, null, 0L);
+		final Modem modem = context.mock(Modem.class);
+		context.checking(new Expectations() {{
+			oneOf(serialPortList).getPortNames();
+			will(returnValue(new String[] { "foo", "bar", "baz" }));
+			// First call to createModem() returns false
+			oneOf(modemFactory).createModem(plugin, "foo");
+			will(returnValue(modem));
+			oneOf(modem).start();
+			will(returnValue(false));
+			// Second call to createModem() throws an exception
+			oneOf(modemFactory).createModem(plugin, "bar");
+			will(returnValue(modem));
+			oneOf(modem).start();
+			will(throwException(new IOException()));
+			// Third call to createModem() returns true
+			oneOf(modemFactory).createModem(plugin, "baz");
+			will(returnValue(modem));
+			oneOf(modem).start();
+			will(returnValue(true));
+		}});
+		assertTrue(plugin.start());
+		context.assertIsSatisfied();
+	}
+
+	@Test
+	public void testCreateConnection() throws Exception {
+		Mockery context = new Mockery();
+		final ModemFactory modemFactory = context.mock(ModemFactory.class);
+		final SerialPortList serialPortList =
+				context.mock(SerialPortList.class);
+		final DuplexPluginCallback callback =
+				context.mock(DuplexPluginCallback.class);
+		final ModemPlugin plugin = new ModemPlugin(null, modemFactory,
+				serialPortList, callback, 0L);
+		final Modem modem = context.mock(Modem.class);
+		final TransportProperties local = new TransportProperties();
+		local.put("iso3166", ISO_1336);
+		TransportProperties p = new TransportProperties();
+		p.put("iso3166", ISO_1336);
+		p.put("number", NUMBER1);
+		ContactId contactId = new ContactId(234);
+		final Map<ContactId, TransportProperties> remote =
+				Collections.singletonMap(contactId, p);
+		context.checking(new Expectations() {{
+			// start()
+			oneOf(serialPortList).getPortNames();
+			will(returnValue(new String[] { "foo" }));
+			oneOf(modemFactory).createModem(plugin, "foo");
+			will(returnValue(modem));
+			oneOf(modem).start();
+			will(returnValue(true));
+			// createConnection()
+			oneOf(callback).getLocalProperties();
+			will(returnValue(local));
+			oneOf(callback).getRemoteProperties();
+			will(returnValue(remote));
+			oneOf(modem).dial(NUMBER1);
+			will(returnValue(true));
+		}});
+		assertTrue(plugin.start());
+		// A connection should be returned
+		assertNotNull(plugin.createConnection(contactId));
+		context.assertIsSatisfied();
+	}
+
+	@Test
+	public void testCreateConnectionWhenDialReturnsFalse() throws Exception {
+		Mockery context = new Mockery();
+		final ModemFactory modemFactory = context.mock(ModemFactory.class);
+		final SerialPortList serialPortList =
+				context.mock(SerialPortList.class);
+		final DuplexPluginCallback callback =
+				context.mock(DuplexPluginCallback.class);
+		final ModemPlugin plugin = new ModemPlugin(null, modemFactory,
+				serialPortList, callback, 0L);
+		final Modem modem = context.mock(Modem.class);
+		final TransportProperties local = new TransportProperties();
+		local.put("iso3166", ISO_1336);
+		TransportProperties p = new TransportProperties();
+		p.put("iso3166", ISO_1336);
+		p.put("number", NUMBER1);
+		ContactId contactId = new ContactId(234);
+		final Map<ContactId, TransportProperties> remote =
+				Collections.singletonMap(contactId, p);
+		context.checking(new Expectations() {{
+			// start()
+			oneOf(serialPortList).getPortNames();
+			will(returnValue(new String[] { "foo" }));
+			oneOf(modemFactory).createModem(plugin, "foo");
+			will(returnValue(modem));
+			oneOf(modem).start();
+			will(returnValue(true));
+			// createConnection()
+			oneOf(callback).getLocalProperties();
+			will(returnValue(local));
+			oneOf(callback).getRemoteProperties();
+			will(returnValue(remote));
+			oneOf(modem).dial(NUMBER1);
+			will(returnValue(false));
+		}});
+		assertTrue(plugin.start());
+		// No connection should be returned
+		assertNull(plugin.createConnection(contactId));
+		context.assertIsSatisfied();
+	}
+
+	@Test
+	public void testCreateConnectionWhenDialThrowsException() throws Exception {
+		Mockery context = new Mockery();
+		final ModemFactory modemFactory = context.mock(ModemFactory.class);
+		final SerialPortList serialPortList =
+				context.mock(SerialPortList.class);
+		final DuplexPluginCallback callback =
+				context.mock(DuplexPluginCallback.class);
+		final ModemPlugin plugin = new ModemPlugin(null, modemFactory,
+				serialPortList, callback, 0L);
+		final Modem modem = context.mock(Modem.class);
+		final TransportProperties local = new TransportProperties();
+		local.put("iso3166", ISO_1336);
+		TransportProperties p = new TransportProperties();
+		p.put("iso3166", ISO_1336);
+		p.put("number", NUMBER1);
+		ContactId contactId = new ContactId(234);
+		final Map<ContactId, TransportProperties> remote =
+				Collections.singletonMap(contactId, p);
+		context.checking(new Expectations() {{
+			// start()
+			oneOf(serialPortList).getPortNames();
+			will(returnValue(new String[] { "foo" }));
+			oneOf(modemFactory).createModem(plugin, "foo");
+			will(returnValue(modem));
+			oneOf(modem).start();
+			will(returnValue(true));
+			// createConnection()
+			oneOf(callback).getLocalProperties();
+			will(returnValue(local));
+			oneOf(callback).getRemoteProperties();
+			will(returnValue(remote));
+			oneOf(modem).dial(NUMBER1);
+			will(throwException(new IOException()));
+			// resetModem()
+			oneOf(serialPortList).getPortNames();
+			will(returnValue(new String[] { "foo" }));
+			oneOf(modemFactory).createModem(plugin, "foo");
+			will(returnValue(modem));
+			oneOf(modem).start();
+			will(returnValue(true));
+		}});
+		assertTrue(plugin.start());
+		// No connection should be returned
+		assertNull(plugin.createConnection(contactId));
+		context.assertIsSatisfied();
+	}
+
+	@Test
+	public void testPolling() throws Exception {
+		final ExecutorService pluginExecutor =
+				Executors.newSingleThreadExecutor();
+		Mockery context = new Mockery();
+		final ModemFactory modemFactory = context.mock(ModemFactory.class);
+		final SerialPortList serialPortList =
+				context.mock(SerialPortList.class);
+		final DuplexPluginCallback callback =
+				context.mock(DuplexPluginCallback.class);
+		final ModemPlugin plugin = new ModemPlugin(pluginExecutor, modemFactory,
+				serialPortList, callback, 0L);
+		final Modem modem = context.mock(Modem.class);
+		final TransportProperties local = new TransportProperties();
+		local.put("iso3166", ISO_1336);
+		TransportProperties p1 = new TransportProperties();
+		p1.put("iso3166", ISO_1336);
+		p1.put("number", NUMBER1);
+		TransportProperties p2 = new TransportProperties();
+		p2.put("iso3166", ISO_1336);
+		p2.put("number", NUMBER2);
+		TransportProperties p3 = new TransportProperties();
+		p3.put("iso3166", ISO_1336);
+		p3.put("number", NUMBER3);
+		ContactId contactId1 = new ContactId(234);
+		ContactId contactId2 = new ContactId(345);
+		ContactId contactId3 = new ContactId(456);
+		final Map<ContactId, TransportProperties> remote =
+				new HashMap<ContactId, TransportProperties>();
+		remote.put(contactId1, p1);
+		remote.put(contactId2, p2);
+		remote.put(contactId3, p3);
+		final DisposeAction disposeAction = new DisposeAction();
+		context.checking(new Expectations() {{
+			// start()
+			oneOf(serialPortList).getPortNames();
+			will(returnValue(new String[] { "foo" }));
+			oneOf(modemFactory).createModem(plugin, "foo");
+			will(returnValue(modem));
+			oneOf(modem).start();
+			will(returnValue(true));
+			// poll()
+			oneOf(callback).getLocalProperties();
+			will(returnValue(local));
+			oneOf(callback).getRemoteProperties();
+			will(returnValue(remote));
+			// First call to dial() throws an exception
+			oneOf(modem).dial(NUMBER1);
+			will(throwException(new IOException()));
+			// resetModem()
+			oneOf(serialPortList).getPortNames();
+			will(returnValue(new String[] { "foo" }));
+			oneOf(modemFactory).createModem(plugin, "foo");
+			will(returnValue(modem));
+			oneOf(modem).start();
+			will(returnValue(true));
+			// Second call to dial() returns true
+			oneOf(modem).dial(NUMBER2);
+			will(returnValue(true));
+			// A connection is passed to the callback - dispose of it
+			oneOf(callback).outgoingConnectionCreated(
+					with(any(ContactId.class)),
+					with(any(DuplexTransportConnection.class)));
+			will(disposeAction);
+			oneOf(modem).hangUp();
+			// Third call to dial() returns false
+			oneOf(modem).dial(NUMBER3);
+			will(returnValue(false));
+		}});
+		assertTrue(plugin.start());
+		plugin.poll(Collections.<ContactId>emptyList());
+		assertTrue(disposeAction.invoked.await(5, SECONDS));
+		pluginExecutor.shutdown();
+		context.assertIsSatisfied();
+	}
+
+	private static class DisposeAction implements Action {
+
+		private final CountDownLatch invoked = new CountDownLatch(1);
+
+		public void describeTo(Description description) {
+			description.appendText("Disposes of a transport connection");
+		}
+
+		public Object invoke(Invocation invocation) throws Throwable {
+			DuplexTransportConnection conn =
+					(DuplexTransportConnection) invocation.getParameter(1);
+			conn.dispose(false, true);
+			invoked.countDown();
+			return null;
+		}
+	}
+}