diff --git a/src/org/openstreetmap/josm/actions/AddImageryLayerAction.java b/src/org/openstreetmap/josm/actions/AddImageryLayerAction.java
index f857d65..c36973c 100644
--- a/src/org/openstreetmap/josm/actions/AddImageryLayerAction.java
+++ b/src/org/openstreetmap/josm/actions/AddImageryLayerAction.java
@@ -78,7 +78,7 @@ public class AddImageryLayerAction extends JosmAction implements AdaptableAction
             final ImageryInfo infoToAdd = ImageryType.WMS_ENDPOINT.equals(info.getImageryType())
                     ? getWMSLayerInfo() : info;
             if (infoToAdd != null) {
-                Main.main.addLayer(ImageryLayer.create(infoToAdd));
+                Main.getLayerManager().addLayer(ImageryLayer.create(infoToAdd));
                 AlignImageryPanel.addNagPanelIfNeeded(infoToAdd);
             }
         } catch (IllegalArgumentException ex) {
diff --git a/src/org/openstreetmap/josm/io/OsmApi.java b/src/org/openstreetmap/josm/io/OsmApi.java
index d661461..8835df1 100644
--- a/src/org/openstreetmap/josm/io/OsmApi.java
+++ b/src/org/openstreetmap/josm/io/OsmApi.java
@@ -89,11 +89,15 @@ public class OsmApi extends OsmConnection {
         OsmApi api = instances.get(serverUrl);
         if (api == null) {
             api = new OsmApi(serverUrl);
-            instances.put(serverUrl, api);
+            cacheInstance(api);
         }
         return api;
     }
 
+    protected static void cacheInstance(OsmApi api) {
+        instances.put(api.getServerUrl(), api);
+    }
+
     private static String getServerUrlFromPref() {
         return Main.pref.get("osm-server.url", DEFAULT_API_URL);
     }
diff --git a/test/unit/org/openstreetmap/josm/actions/AddImageryLayerActionTest.java b/test/unit/org/openstreetmap/josm/actions/AddImageryLayerActionTest.java
index da3a1f8..8a56466 100644
--- a/test/unit/org/openstreetmap/josm/actions/AddImageryLayerActionTest.java
+++ b/test/unit/org/openstreetmap/josm/actions/AddImageryLayerActionTest.java
@@ -7,26 +7,24 @@ import static org.junit.Assert.assertTrue;
 
 import java.util.List;
 
-import org.junit.BeforeClass;
+import org.junit.Rule;
 import org.junit.Test;
-import org.openstreetmap.josm.JOSMFixture;
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.imagery.ImageryInfo;
 import org.openstreetmap.josm.gui.layer.TMSLayer;
 import org.openstreetmap.josm.gui.layer.WMSLayer;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
 
 /**
  * Unit tests for class {@link AddImageryLayerAction}.
  */
 public final class AddImageryLayerActionTest {
-
     /**
-     * Setup test.
+     * We need prefs for this. We need platform for actions and the OSM API for checking blacklist.
+     * @since xxx
      */
-    @BeforeClass
-    public static void setUp() {
-        JOSMFixture.createUnitTestFixture().init(true);
-    }
+    @Rule
+    public JOSMTestRules test = new JOSMTestRules().preferences().platform().fakeAPI();
 
     /**
      * Unit test of {@link AddImageryLayerAction#updateEnabledState}.
@@ -34,7 +32,6 @@ public final class AddImageryLayerActionTest {
     @Test
     public void testEnabledState() {
         assertFalse(new AddImageryLayerAction(new ImageryInfo()).isEnabled());
-        assertFalse(new AddImageryLayerAction(new ImageryInfo("google", "http://maps.google.com/api", "tms", null, null)).isEnabled());
         assertTrue(new AddImageryLayerAction(new ImageryInfo("foo_tms", "http://bar", "tms", null, null)).isEnabled());
         assertTrue(new AddImageryLayerAction(new ImageryInfo("foo_bing", "http://bar", "bing", null, null)).isEnabled());
         assertTrue(new AddImageryLayerAction(new ImageryInfo("foo_scanex", "http://bar", "scanex", null, null)).isEnabled());
@@ -42,25 +39,30 @@ public final class AddImageryLayerActionTest {
     }
 
     /**
+     * Unit test of {@link AddImageryLayerAction#updateEnabledState} respects blacklist.
+     * @since xxx
+     */
+    @Test
+    public void testEnabledStateBlacklist() {
+        assertFalse(new AddImageryLayerAction(new ImageryInfo("google", "http://blacklisted", "tms", null, null)).isEnabled());
+        assertFalse(new AddImageryLayerAction(new ImageryInfo("google", "https://invalid", "tms", null, null)).isEnabled());
+        assertTrue(new AddImageryLayerAction(new ImageryInfo("google", "https://notinvalid", "tms", null, null)).isEnabled());
+    }
+
+    /**
      * Unit test of {@link AddImageryLayerAction#actionPerformed} - Enabled cases.
      */
     @Test
     public void testActionPerformedEnabled() {
-        assertTrue(Main.map.mapView.getLayersOfType(TMSLayer.class).isEmpty());
+        assertTrue(Main.getLayerManager().getLayersOfType(TMSLayer.class).isEmpty());
         new AddImageryLayerAction(new ImageryInfo("foo_tms", "http://bar", "tms", null, null)).actionPerformed(null);
-        List<TMSLayer> tmsLayers = Main.map.mapView.getLayersOfType(TMSLayer.class);
+        List<TMSLayer> tmsLayers = Main.getLayerManager().getLayersOfType(TMSLayer.class);
         assertEquals(1, tmsLayers.size());
 
-        try {
-            new AddImageryLayerAction(new ImageryInfo("wms.openstreetmap.fr", "http://wms.openstreetmap.fr/wms?",
-                    "wms_endpoint", null, null)).actionPerformed(null);
-            List<WMSLayer> wmsLayers = Main.map.mapView.getLayersOfType(WMSLayer.class);
-            assertEquals(1, wmsLayers.size());
-
-            Main.map.mapView.removeLayer(wmsLayers.get(0));
-        } finally {
-            Main.map.mapView.removeLayer(tmsLayers.get(0));
-        }
+        new AddImageryLayerAction(new ImageryInfo("wms.openstreetmap.fr", "http://wms.openstreetmap.fr/wms?",
+                "wms_endpoint", null, null)).actionPerformed(null);
+        List<WMSLayer> wmsLayers = Main.getLayerManager().getLayersOfType(WMSLayer.class);
+        assertEquals(1, wmsLayers.size());
     }
 
     /**
@@ -68,8 +70,8 @@ public final class AddImageryLayerActionTest {
      */
     @Test
     public void testActionPerformedDisabled() {
-        assertTrue(Main.map.mapView.getLayersOfType(TMSLayer.class).isEmpty());
+        assertTrue(Main.getLayerManager().getLayersOfType(TMSLayer.class).isEmpty());
         new AddImageryLayerAction(new ImageryInfo()).actionPerformed(null);
-        assertTrue(Main.map.mapView.getLayersOfType(TMSLayer.class).isEmpty());
+        assertTrue(Main.getLayerManager().getLayersOfType(TMSLayer.class).isEmpty());
     }
 }
diff --git a/test/unit/org/openstreetmap/josm/actions/search/SearchCompilerTest.java b/test/unit/org/openstreetmap/josm/actions/search/SearchCompilerTest.java
index 097d1e3..bb63195 100644
--- a/test/unit/org/openstreetmap/josm/actions/search/SearchCompilerTest.java
+++ b/test/unit/org/openstreetmap/josm/actions/search/SearchCompilerTest.java
@@ -4,10 +4,10 @@ package org.openstreetmap.josm.actions.search;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
-import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
-import org.openstreetmap.josm.JOSMFixture;
 import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
 import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
 import org.openstreetmap.josm.data.coor.LatLon;
@@ -22,6 +22,7 @@ import org.openstreetmap.josm.data.osm.Tag;
 import org.openstreetmap.josm.data.osm.User;
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.WayData;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
 import org.openstreetmap.josm.tools.date.DateUtils;
 
 /**
@@ -30,12 +31,11 @@ import org.openstreetmap.josm.tools.date.DateUtils;
 public class SearchCompilerTest {
 
     /**
-     * Setup test.
+     * We need prefs for this. We access preferences when creating OSM primitives.
+     * @since xxx
      */
-    @Before
-    public void setUp() {
-        JOSMFixture.createUnitTestFixture().init();
-    }
+    @Rule
+    public JOSMTestRules test = new JOSMTestRules().preferences();
 
     private static final class SearchContext {
         final DataSet ds = new DataSet();
@@ -389,7 +389,7 @@ public class SearchCompilerTest {
     public void testFooTypeBar() {
         try {
             SearchCompiler.compile("foo type bar");
-            throw new RuntimeException();
+            fail();
         } catch (ParseError parseError) {
             assertEquals("<html>Expecting <code>:</code> after <i>type</i>", parseError.getMessage());
         }
diff --git a/test/unit/org/openstreetmap/josm/testutils/FakeOsmApi.java b/test/unit/org/openstreetmap/josm/testutils/FakeOsmApi.java
new file mode 100644
index 0000000..5d45f85
--- /dev/null
+++ b/test/unit/org/openstreetmap/josm/testutils/FakeOsmApi.java
@@ -0,0 +1,122 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.testutils;
+
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.notes.Note;
+import org.openstreetmap.josm.data.osm.Changeset;
+import org.openstreetmap.josm.data.osm.IPrimitive;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.io.Capabilities;
+import org.openstreetmap.josm.io.OsmApi;
+import org.openstreetmap.josm.io.OsmApiInitializationException;
+import org.openstreetmap.josm.io.OsmTransferCanceledException;
+import org.openstreetmap.josm.io.OsmTransferException;
+
+/**
+ * A fake OSM API server. It is used to test again.
+ * <p>
+ * It provides only basic features.
+ * <p>
+ * These image servers are blacklisted:
+ * <ul>
+ * <li>.*blacklisted.*</li>
+ * <li>^(invalid|bad).*</li>
+ * </ul>
+ *
+ * @author Michael Zangl
+ * @since xxx
+ */
+public class FakeOsmApi extends OsmApi {
+
+    private static FakeOsmApi instance;
+
+    private boolean initialized = false;
+
+    protected FakeOsmApi() {
+        super("http://fake.xxx/api");
+    }
+
+    @Override
+    public void initialize(ProgressMonitor monitor, boolean fastFail)
+            throws OsmTransferCanceledException, OsmApiInitializationException {
+        // we do not connect to any server so we do not need that.
+        initialized = true;
+    }
+
+    @Override
+    public synchronized Capabilities getCapabilities() {
+        if (!initialized) {
+            return null;
+        } else {
+            Capabilities capabilities = new Capabilities();
+            capabilities.put("blacklist", "regex", ".*blacklisted.*");
+            capabilities.put("blacklist", "regex", "^https?://(invalid|bad).*");
+            capabilities.put("version", "minimum", "0.6");
+            capabilities.put("version", "maximum", "0.6");
+            return capabilities;
+        }
+    }
+
+    @Override
+    public void createPrimitive(IPrimitive osm, ProgressMonitor monitor) throws OsmTransferException {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public void modifyPrimitive(IPrimitive osm, ProgressMonitor monitor) throws OsmTransferException {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public void deletePrimitive(OsmPrimitive osm, ProgressMonitor monitor) throws OsmTransferException {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public void openChangeset(Changeset changeset, ProgressMonitor progressMonitor) throws OsmTransferException {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public void updateChangeset(Changeset changeset, ProgressMonitor monitor) throws OsmTransferException {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public void closeChangeset(Changeset changeset, ProgressMonitor monitor) throws OsmTransferException {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public Note createNote(LatLon latlon, String text, ProgressMonitor monitor) throws OsmTransferException {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public Note addCommentToNote(Note note, String comment, ProgressMonitor monitor) throws OsmTransferException {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public Note closeNote(Note note, String closeMessage, ProgressMonitor monitor) throws OsmTransferException {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public Note reopenNote(Note note, String reactivateMessage, ProgressMonitor monitor) throws OsmTransferException {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    /**
+     * Gets and caches an instance of this API.
+     * @return The API intance. Always the same object.
+     */
+    public static synchronized FakeOsmApi getInstance() {
+        if (instance == null) {
+            instance = new FakeOsmApi();
+            cacheInstance(instance);
+        }
+        return instance;
+    }
+}
diff --git a/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java b/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java
new file mode 100644
index 0000000..47a25ce
--- /dev/null
+++ b/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java
@@ -0,0 +1,227 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.testutils;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.junit.rules.TemporaryFolder;
+import org.junit.rules.TestRule;
+import org.junit.rules.Timeout;
+import org.junit.runner.Description;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.layer.MainLayerManager;
+import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.io.OsmApi;
+import org.openstreetmap.josm.io.OsmApiInitializationException;
+import org.openstreetmap.josm.io.OsmTransferCanceledException;
+import org.openstreetmap.josm.tools.I18n;
+
+/**
+ * This class runs a test in an environment that resembles the one used by the JOSM main application.
+ * <p>
+ * The environment is reset before every test. You can specify the components to which you need access using the methods of this class.
+ * For example, invoking {@link #preferences()} gives you access to the (default) preferences.
+ *
+ * @author Michael Zangl
+ * @since xxx
+ */
+public class JOSMTestRules implements TestRule {
+    //We should make this the default when running from ant: Timeout.seconds(10);
+    private Timeout timeout = null;
+    private TemporaryFolder josmHome;
+    private boolean usePreferences = false;
+    private APIType useAPI = APIType.NONE;
+    private String i18n = null;
+    private boolean platform;
+
+    /**
+     * Disable the default timeout for this test. Use with care.
+     * @return this instance, for easy chaining
+     */
+    public JOSMTestRules noTimeout() {
+        timeout = null;
+        return this;
+    }
+
+    /**
+     * Set a timeout for all tests in this class. Local method timeouts may only reduce this timeout.
+     * @param millis The timeout duration in milliseconds.
+     * @return this instance, for easy chaining
+     */
+    public JOSMTestRules timeout(int millis) {
+        timeout = Timeout.millis(millis);
+        return this;
+    }
+
+    /**
+     * Enable the use of default preferences.
+     * @return this instance, for easy chaining
+     */
+    public JOSMTestRules preferences() {
+        josmHome();
+        usePreferences = true;
+        return this;
+    }
+
+    /**
+     * Set JOSM home to a valid, empty directory.
+     * @return this instance, for easy chaining
+     */
+    private JOSMTestRules josmHome() {
+        josmHome = new TemporaryFolder();
+        return this;
+    }
+
+    /**
+     * Enables the i18n module for this test in english.
+     * @return this instance, for easy chaining
+     */
+    public JOSMTestRules i18n() {
+        return i18n("en");
+    }
+
+    /**
+     * Enables the i18n module for this test.
+     * @param language The language to use.
+     * @return this instance, for easy chaining
+     */
+    public JOSMTestRules i18n(String language) {
+        i18n = language;
+        return this;
+    }
+
+    /**
+     * Enable {@link Main#platform} global variable.
+     * @return this instance, for easy chaining
+     */
+    public JOSMTestRules platform() {
+        platform = true;
+        return this;
+    }
+
+    /**
+     * Enable the dev.openstreetmap.org API for this test.
+     * @return this instance, for easy chaining
+     */
+    public JOSMTestRules devAPI() {
+        preferences();
+        useAPI = APIType.DEV;
+        return this;
+    }
+
+    /**
+     * Use the {@link FakeOsmApi} for testing.
+     * @return this instance, for easy chaining
+     */
+    public JOSMTestRules fakeAPI() {
+        useAPI = APIType.FAKE;
+        return this;
+    }
+
+    @Override
+    public Statement apply(final Statement base, Description description) {
+        Statement statement = new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                before();
+                try {
+                    base.evaluate();
+                } finally {
+                    after();
+                }
+            }
+        };
+        if (timeout != null) {
+            statement = timeout.apply(statement, description);
+        }
+        if (josmHome != null) {
+            statement = josmHome.apply(statement, description);
+        }
+        return statement;
+    }
+
+    /**
+     * Set up before running a test
+     * @throws InitializationError If an error occured while creating the required environment.
+     */
+    protected void before() throws InitializationError {
+        // Tests are running headless by default.
+        System.setProperty("java.awt.headless", "true");
+
+        // Set up i18n
+        if (i18n != null) {
+            I18n.set(i18n);
+        }
+
+        // Add JOSM home
+        if (josmHome != null) {
+            try {
+                File home = josmHome.newFolder();
+                System.setProperty("josm.home", home.getAbsolutePath());
+            } catch (IOException e) {
+                throw new InitializationError(e);
+            }
+        }
+
+        // Add preferences
+        if (usePreferences) {
+            Main.initApplicationPreferences();
+            Main.pref.enableSaveOnPut(false);
+            // No pref init -> that would only create the preferences file.
+            // We force the use of a wrong API server, just in case anyone attempts an upload
+            Main.pref.put("osm-server.url", "http://invalid");
+        }
+
+        // Set API
+        if (useAPI == APIType.DEV) {
+            Main.pref.put("osm-server.url", "http://api06.dev.openstreetmap.org/api");
+        } else if (useAPI == APIType.FAKE) {
+            FakeOsmApi api = FakeOsmApi.getInstance();
+            Main.pref.put("osm-server.url", api.getServerUrl());
+        }
+
+        // Initialize API
+        if (useAPI != APIType.NONE) {
+            try {
+                OsmApi.getOsmApi().initialize(null);
+            } catch (OsmTransferCanceledException | OsmApiInitializationException e) {
+                throw new InitializationError(e);
+            }
+        }
+
+        // Set Platform
+        if (platform) {
+            Main.determinePlatformHook();
+        }
+    }
+
+    /**
+     * Clean up after running a test
+     */
+    protected void after() {
+        // Sync AWT Thread
+        GuiHelper.runInEDTAndWait(new Runnable() {
+            @Override
+            public void run() {
+            }
+        });
+        // Remove all layers
+        MainLayerManager lm = Main.getLayerManager();
+        while (!lm.getLayers().isEmpty()) {
+            lm.removeLayer(lm.getLayers().get(0));
+        }
+
+        // TODO: Remove global listeners and other global state.
+
+        Main.pref = null;
+        Main.platform = null;
+        // Parts of JOSM uses weak references - destroy them.
+        System.gc();
+    }
+
+    enum APIType {
+        NONE, FAKE, DEV
+    }
+}
