diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/task/UserManagementMaintenanceTask.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/task/UserManagementMaintenanceTask.java
index 494c616e7340d950e3235f764e9da725aabf9206..c0d98921e66941e8b2c2ad916890f19d89f81599 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/task/UserManagementMaintenanceTask.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/task/UserManagementMaintenanceTask.java
@@ -88,7 +88,6 @@ public class UserManagementMaintenanceTask implements IMaintenanceTask
                     + "'ldap.security.principal.password' have to be specified in 'service.properties'.");
         }
         operationLog.info("Plugin '" + pluginName + "' initialized. Configuration file: " + configurationFile.getAbsolutePath());
-
     }
 
     @Override
@@ -99,9 +98,9 @@ public class UserManagementMaintenanceTask implements IMaintenanceTask
         {
             return;
         }
+        operationLog.info("manage " + config.getGroups().size() + " groups");
         Log4jSimpleLogger logger = new Log4jSimpleLogger(operationLog);
-        UserManager userManager = new UserManager(ldapService, getService(),
-                config.getCommonSpaces(), logger, getTimeProvider());
+        UserManager userManager = createUserManager(config, logger);
         for (UserGroup group : config.getGroups())
         {
             if (addGroup(userManager, group) == false)
@@ -190,14 +189,9 @@ public class UserManagementMaintenanceTask implements IMaintenanceTask
         return (LDAPAuthenticationService) CommonServiceProvider.getApplicationContext().getBean("ldap-authentication-service");
     }
 
-    protected IApplicationServerInternalApi getService()
+    protected UserManager createUserManager(UserManagerConfig config, Log4jSimpleLogger logger)
     {
-        return CommonServiceProvider.getApplicationServerApi();
+        return new UserManager(ldapService, CommonServiceProvider.getApplicationServerApi(),
+                config.getCommonSpaces(), logger, SystemTimeProvider.SYSTEM_TIME_PROVIDER);
     }
-
-    protected ITimeAndWaitingProvider getTimeProvider()
-    {
-        return SystemTimeProvider.SYSTEM_TIME_PROVIDER;
-    }
-
 }
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/task/UserManagementMaintenanceTaskTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/task/UserManagementMaintenanceTaskTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9c2830ef8435b049cf785d88541d592a51eac920
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/task/UserManagementMaintenanceTaskTest.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright 2018 ETH Zuerich, SIS
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.server.task;
+
+import static ch.systemsx.cisd.common.test.AssertionUtil.assertContains;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.TreeMap;
+
+import org.apache.log4j.Level;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.dto.roleassignment.Role;
+import ch.systemsx.cisd.authentication.Principal;
+import ch.systemsx.cisd.authentication.ldap.LDAPAuthenticationService;
+import ch.systemsx.cisd.authentication.ldap.LDAPDirectoryConfiguration;
+import ch.systemsx.cisd.base.tests.AbstractFileSystemTestCase;
+import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException;
+import ch.systemsx.cisd.common.filesystem.FileUtilities;
+import ch.systemsx.cisd.common.logging.BufferedAppender;
+import ch.systemsx.cisd.common.logging.ISimpleLogger;
+import ch.systemsx.cisd.common.logging.Log4jSimpleLogger;
+import ch.systemsx.cisd.common.logging.LogLevel;
+import ch.systemsx.cisd.common.maintenance.IMaintenanceTask;
+import ch.systemsx.cisd.common.shared.basic.string.CommaSeparatedListBuilder;
+import ch.systemsx.cisd.common.utilities.MockTimeProvider;
+import ch.systemsx.cisd.openbis.util.LogRecordingUtils;
+import de.schlichtherle.io.File;
+
+/**
+ * @author Franz-Josef Elmer
+ */
+public class UserManagementMaintenanceTaskTest extends AbstractFileSystemTestCase
+{
+    private static final Principal U1 = new Principal("u1", "Albert", "Einstein", "a.e@abc.de");
+
+    private BufferedAppender logRecorder;
+
+    private File configFile;
+
+    private File auditLogFile;
+
+    private Properties properties;
+
+    @BeforeMethod
+    public void setUp() throws IOException
+    {
+        super.setUp();
+        logRecorder = LogRecordingUtils.createRecorder("%-5p %c - %m%n", Level.DEBUG);
+        configFile = new File(workingDirectory, "config.json");
+        auditLogFile = new File(workingDirectory, "audit_log.txt");
+        properties = new Properties();
+        properties.setProperty(UserManagementMaintenanceTask.CONFIGURATION_FILE_PATH_PROPERTY, configFile.getPath());
+        properties.setProperty(UserManagementMaintenanceTask.AUDIT_LOG_FILE_PATH_PROPERTY, auditLogFile.getPath());
+    }
+
+    @Test
+    public void testSetUpFailedBecauseConfigFileDoesNotExist()
+    {
+        // Given
+        UserManagementMaintenanceTaskWithMocks task = new UserManagementMaintenanceTaskWithMocks();
+        properties.remove(UserManagementMaintenanceTask.CONFIGURATION_FILE_PATH_PROPERTY);
+
+        // When + Then
+        assertConfigFailure(task, UserManagementMaintenanceTask.DEFAULT_CONFIGURATION_FILE_PATH + "' doesn't exist or is a directory.");
+    }
+
+    @Test
+    public void testSetUpFailedBecauseConfigFileIsADirectory()
+    {
+        // Given
+        UserManagementMaintenanceTaskWithMocks task = new UserManagementMaintenanceTaskWithMocks();
+        configFile.mkdirs();
+
+        // When + Then
+        assertConfigFailure(task, configFile.getPath() + "' doesn't exist or is a directory.");
+    }
+
+    @Test
+    public void testSetUpFailedBecauseAuditFileIsADirectory()
+    {
+        // Given
+        UserManagementMaintenanceTaskWithMocks task = new UserManagementMaintenanceTaskWithMocks();
+        FileUtilities.writeToFile(configFile, "");
+        auditLogFile.mkdirs();
+
+        // When + Then
+        assertConfigFailure(task, auditLogFile.getPath() + "' is a directory.");
+    }
+
+    @Test
+    public void testSetUpFailedBecauseLdapServiceIsNotConfigured()
+    {
+        // Given
+        UserManagementMaintenanceTaskWithMocks task = new UserManagementMaintenanceTaskWithMocks().withNotConfiguredLdap();
+        FileUtilities.writeToFile(configFile, "");
+
+        // When + Then
+        assertConfigFailure(task, "There is no LDAP authentication service configured.");
+    }
+
+    @Test
+    public void testExecuteFailedBecauseOfConfigFileHasBeenDeleted()
+    {
+        // Given
+        UserManagementMaintenanceTaskWithMocks task = new UserManagementMaintenanceTaskWithMocks();
+        FileUtilities.writeToFile(configFile, "");
+        task.setUp("", properties);
+        configFile.delete();
+
+        // When
+        task.execute();
+
+        // Then
+        assertContains(configFile + "' doesn't exist", logRecorder.getLogContent());
+    }
+
+    @Test
+    public void testExecuteFailedBecauseOfInvalidConfigFile()
+    {
+        // Given
+        UserManagementMaintenanceTaskWithMocks task = new UserManagementMaintenanceTaskWithMocks();
+        FileUtilities.writeToFile(configFile, "");
+        task.setUp("", properties);
+
+        // When
+        task.execute();
+
+        // Then
+        assertContains("ERROR OPERATION.UserManagementMaintenanceTask - Invalid content of configuration file '"
+                + configFile.getAbsolutePath(), logRecorder.getLogContent());
+    }
+
+    @Test
+    public void testExecuteNoGroups()
+    {
+        // Given
+        UserManagementMaintenanceTaskWithMocks task = new UserManagementMaintenanceTaskWithMocks();
+        FileUtilities.writeToFile(configFile, "");
+        task.setUp("", properties);
+        FileUtilities.writeToFile(configFile, "{}");
+
+        // When
+        task.execute();
+
+        // Then
+        assertEquals("INFO  OPERATION.UserManagementMaintenanceTask - Setup plugin \n"
+                + "INFO  OPERATION.UserManagementMaintenanceTask - Plugin '' initialized. Configuration file: "
+                + configFile.getAbsolutePath(), logRecorder.getLogContent());
+    }
+
+    @Test
+    public void testExecuteMissingLdapGroupKeys()
+    {
+        // Given
+        UserManagementMaintenanceTaskWithMocks task = new UserManagementMaintenanceTaskWithMocks();
+        FileUtilities.writeToFile(configFile, "");
+        task.setUp("", properties);
+        FileUtilities.writeToFile(configFile, "{\"groups\": [{\"key\":\"ABC\"}]}");
+
+        // When
+        task.execute();
+
+        // Then
+        assertEquals("INFO  OPERATION.UserManagementMaintenanceTask - Setup plugin \n"
+                + "INFO  OPERATION.UserManagementMaintenanceTask - Plugin '' initialized. Configuration file: "
+                + configFile.getAbsolutePath() + "\n"
+                + "INFO  OPERATION.UserManagementMaintenanceTask - manage 1 groups\n"
+                + "DEBUG OPERATION.UserManagementMaintenanceTask - Common spaces: {}\n"
+                + "ERROR OPERATION.UserManagementMaintenanceTask - No ldapGroupKeys specified for group 'ABC'. Task aborted.",
+                logRecorder.getLogContent());
+    }
+
+    @Test
+    public void testExecuteEmptyLdapGroupKeys()
+    {
+        // Given
+        UserManagementMaintenanceTaskWithMocks task = new UserManagementMaintenanceTaskWithMocks();
+        FileUtilities.writeToFile(configFile, "");
+        task.setUp("", properties);
+        FileUtilities.writeToFile(configFile, "{\"groups\": [{\"key\":\"ABC\",\"ldapGroupKeys\":[\"\"]}]}");
+
+        // When
+        task.execute();
+
+        // Then
+        assertEquals("INFO  OPERATION.UserManagementMaintenanceTask - Setup plugin \n"
+                + "INFO  OPERATION.UserManagementMaintenanceTask - Plugin '' initialized. Configuration file: "
+                + configFile.getAbsolutePath() + "\n"
+                + "INFO  OPERATION.UserManagementMaintenanceTask - manage 1 groups\n"
+                + "DEBUG OPERATION.UserManagementMaintenanceTask - Common spaces: {}\n"
+                + "ERROR OPERATION.UserManagementMaintenanceTask - Empty ldapGroupKey for group 'ABC'. Task aborted.",
+                logRecorder.getLogContent());
+    }
+
+    @Test
+    public void testExecuteEmptyLdapGroups()
+    {
+        // Given
+        UserManagementMaintenanceTaskWithMocks task = new UserManagementMaintenanceTaskWithMocks();
+        FileUtilities.writeToFile(configFile, "");
+        task.setUp("", properties);
+        FileUtilities.writeToFile(configFile, "{\"groups\": [{\"key\":\"ABC\",\"ldapGroupKeys\":[\"a1\"]}]}");
+
+        // When
+        task.execute();
+
+        // Then
+        assertEquals("INFO  OPERATION.UserManagementMaintenanceTask - Setup plugin \n"
+                + "INFO  OPERATION.UserManagementMaintenanceTask - Plugin '' initialized. Configuration file: "
+                + configFile.getAbsolutePath() + "\n"
+                + "INFO  OPERATION.UserManagementMaintenanceTask - manage 1 groups\n"
+                + "DEBUG OPERATION.UserManagementMaintenanceTask - Common spaces: {}\n"
+                + "ERROR OPERATION.UserManagementMaintenanceTask - No users found for ldapGroupKey 'a1' for group 'ABC'. Task aborted.",
+                logRecorder.getLogContent());
+    }
+
+    @Test
+    public void testExecuteHappyCase()
+    {
+        // Given
+        UserManagerReport report = new UserManagerReport(new MockTimeProvider(0, 1000));
+        report.addErrorMessage("This is a test error message");
+        report.addGroup("blabla");
+        UserManagementMaintenanceTaskWithMocks task = new UserManagementMaintenanceTaskWithMocks().withGroup("s", U1)
+                .withUserManagerReport(report);
+        FileUtilities.writeToFile(configFile, "");
+        task.setUp("", properties);
+        FileUtilities.writeToFile(configFile, "{\"commonSpaces\":{\"USER\": [\"ALPHA\"]},"
+                + "\"groups\": [{\"name\":\"sis\",\"key\":\"SIS\",\"ldapGroupKeys\": [\"s\"],\"admins\": [\"u2\"]}]}");
+
+        // When
+        task.execute();
+
+        // Then
+        assertEquals("INFO  OPERATION.UserManagementMaintenanceTask - Setup plugin \n"
+                + "INFO  OPERATION.UserManagementMaintenanceTask - Plugin '' initialized. Configuration file: "
+                + configFile.getAbsolutePath() + "\n"
+                + "INFO  OPERATION.UserManagementMaintenanceTask - manage 1 groups\n"
+                + "DEBUG OPERATION.UserManagementMaintenanceTask - Common spaces: {USER=[ALPHA]}\n"
+                + "DEBUG OPERATION.UserManagementMaintenanceTask - Add group SIS[name:sis, ldapGroupKeys:[s], admins:[u2]] with users [u1=u1]\n"
+                + "INFO  OPERATION.UserManagementMaintenanceTask - 1 users for group SIS\n"
+                + "ERROR NOTIFY.UserManagementMaintenanceTask - User management failed for the following reason(s):\n\n"
+                + "This is a test error message\n\n"
+                + "INFO  OPERATION.UserManagementMaintenanceTask - finished",
+                logRecorder.getLogContent());
+    }
+
+    private void assertConfigFailure(IMaintenanceTask task, String errorMessageSnippet)
+    {
+        try
+        {
+            task.setUp("", properties);
+            fail("ConfigurationFailureException expected");
+        } catch (ConfigurationFailureException e)
+        {
+            assertContains(errorMessageSnippet, e.getMessage());
+        }
+    }
+
+    private final class UserManagementMaintenanceTaskWithMocks extends UserManagementMaintenanceTask
+    {
+        private Map<String, List<Principal>> usersByGroup = new TreeMap<>();
+
+        private LDAPDirectoryConfiguration config;
+
+        private UserManagerReport report;
+
+        private UserManagementMaintenanceTaskWithMocks()
+        {
+            config = new LDAPDirectoryConfiguration();
+            config.setServerUrl("blabla");
+            config.setSecurityPrincipalDistinguishedName("blabla");
+            config.setSecurityPrincipalPassword("blabla");
+        }
+
+        @Override
+        protected List<Principal> getUsersOfGroup(String ldapGroupKey)
+        {
+            List<Principal> users = usersByGroup.get(ldapGroupKey);
+            return users == null ? new LinkedList<>() : users;
+        }
+
+        @Override
+        protected LDAPAuthenticationService getLdapAuthenticationService()
+        {
+            return new LDAPAuthenticationService(config);
+        }
+
+        @Override
+        protected UserManager createUserManager(UserManagerConfig config, Log4jSimpleLogger logger)
+        {
+            return new MockUserManager(config.getCommonSpaces(), logger);
+        }
+
+        private UserManagementMaintenanceTaskWithMocks withGroup(String groupCode, Principal... users)
+        {
+            usersByGroup.put(groupCode, Arrays.asList(users));
+            return this;
+        }
+
+        private UserManagementMaintenanceTaskWithMocks withNotConfiguredLdap()
+        {
+            config.setSecurityPrincipalPassword(null);
+            return this;
+        }
+
+        private UserManagementMaintenanceTaskWithMocks withUserManagerReport(UserManagerReport report)
+        {
+            this.report = report;
+            return this;
+        }
+
+        private final class MockUserManager extends UserManager
+        {
+            private ISimpleLogger logger;
+
+            MockUserManager(Map<Role, List<String>> commonSpacesByRole, ISimpleLogger logger)
+            {
+                super(null, null, commonSpacesByRole, logger, null);
+                this.logger = logger;
+                logger.log(LogLevel.DEBUG, "Common spaces: " + commonSpacesByRole);
+            }
+
+            @Override
+            public void addGroup(UserGroup group, Map<String, Principal> principalsByUserId)
+            {
+                String renderedGroup = group.getKey() + "[name:" + group.getName() + ", ldapGroupKeys:"
+                        + group.getLdapGroupKeys() + ", admins:" + group.getAdmins() + "]";
+                CommaSeparatedListBuilder builder = new CommaSeparatedListBuilder();
+                for (Entry<String, Principal> entry : principalsByUserId.entrySet())
+                {
+                    builder.append(entry.getKey() + "=" + entry.getValue().getUserId());
+                }
+                logger.log(LogLevel.DEBUG, "Add group " + renderedGroup + " with users [" + builder + "]");
+                super.addGroup(group, principalsByUserId);
+            }
+
+            @Override
+            public UserManagerReport manage()
+            {
+                return report;
+            }
+        }
+    }
+
+}