diff --git a/dspace-api/src/test/data/dspaceFolder/config/modules/curate.cfg b/dspace-api/src/test/data/dspaceFolder/config/modules/curate.cfg
new file mode 100644
index 0000000000..92ba93ca38
--- /dev/null
+++ b/dspace-api/src/test/data/dspaceFolder/config/modules/curate.cfg
@@ -0,0 +1,29 @@
+#---------------------------------------------------------------#
+#--------------CURATION SYSTEM CONFIGURATIONS-------------------#
+#---------------------------------------------------------------#
+# Configuration properties used solely by the Curation system #
+#---------------------------------------------------------------#
+
+### Task Class implementations
+
+# NOTE: Other configurations can append to this list of default tasks by simply
+# adding their own additional values of "plugin.named.org.dspace.curate.CurationTask"
+plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.NoOpCurationTask = noop
+plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.ProfileFormats = profileformats
+plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.RequiredMetadata = requiredmetadata
+#plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.ClamScan = vscan
+#plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.MicrosoftTranslator = translate
+plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.general.MetadataValueLinkChecker = checklinks
+# add new tasks here (or in additional config files)
+
+# Testing tasks
+plugin.named.org.dspace.curate.CurationTask = org.dspace.ctask.testing.MarkerTask = marker
+
+## task queue implementation
+plugin.single.org.dspace.curate.TaskQueue = org.dspace.curate.FileTaskQueue
+
+# directory location of curation task queues
+curate.taskqueue.dir = ${dspace.dir}/ctqueues
+
+# (optional) directory location of scripted (non-java) tasks
+# curate.script.dir = ${dspace.dir}/ctscripts
diff --git a/dspace-api/src/test/java/org/dspace/util/DSpaceConfigurationInitializer.java b/dspace-api/src/test/java/org/dspace/util/DSpaceConfigurationInitializer.java
new file mode 100644
index 0000000000..70d8f12990
--- /dev/null
+++ b/dspace-api/src/test/java/org/dspace/util/DSpaceConfigurationInitializer.java
@@ -0,0 +1,57 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.util;
+
+import org.apache.commons.configuration2.Configuration;
+import org.apache.commons.configuration2.spring.ConfigurationPropertySource;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.dspace.services.ConfigurationService;
+import org.dspace.services.factory.DSpaceServicesFactory;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+
+/**
+ * Utility class that will initialize the DSpace Configuration on Spring startup.
+ * Adapted from the class of the same name in {@code dspace-server-webapp}.
+ *
+ * NOTE: MUST be loaded after DSpaceKernelInitializer, as it requires the kernel
+ * is already initialized.
+ *
+ * This initializer ensures that our DSpace Configuration is loaded into Spring's
+ * list of {@code PropertySource}s very early in the Spring startup process.
+ * That is important as it allows us to use DSpace configurations within
+ * {@code @ConditionalOnProperty} annotations on beans, as well as {@code @Value}
+ * annotations and XML bean definitions.
+ */
+public class DSpaceConfigurationInitializer
+ implements ApplicationContextInitializer {
+
+ private static final Logger log = LogManager.getLogger();
+
+ @Override
+ public void initialize(final ConfigurableApplicationContext applicationContext) {
+ // Load DSpace Configuration service (requires kernel already initialized)
+ ConfigurationService configurationService
+ = DSpaceServicesFactory.getInstance().getConfigurationService();
+ Configuration configuration = configurationService.getConfiguration();
+
+ // Create an Apache Commons Configuration Property Source from our configuration
+ ConfigurationPropertySource apacheCommonsConfigPropertySource =
+ new ConfigurationPropertySource(configuration.getClass().getName(), configuration);
+
+ // Prepend it to the Environment's list of PropertySources
+ // NOTE: This is added *first* in the list so that settings in DSpace's
+ // ConfigurationService *override* any default values in Spring's
+ // application.properties (or similar).
+ applicationContext.getEnvironment()
+ .getPropertySources()
+ .addFirst(apacheCommonsConfigPropertySource);
+ }
+}
+
diff --git a/dspace-api/src/test/java/org/dspace/util/DSpaceKernelInitializer.java b/dspace-api/src/test/java/org/dspace/util/DSpaceKernelInitializer.java
new file mode 100644
index 0000000000..93fd308185
--- /dev/null
+++ b/dspace-api/src/test/java/org/dspace/util/DSpaceKernelInitializer.java
@@ -0,0 +1,135 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.util;
+
+import java.io.File;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.dspace.kernel.DSpaceKernel;
+import org.dspace.kernel.DSpaceKernelManager;
+import org.dspace.servicemanager.DSpaceKernelImpl;
+import org.dspace.servicemanager.DSpaceKernelInit;
+import org.dspace.servicemanager.config.DSpaceConfigurationService;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.event.ContextClosedEvent;
+import org.springframework.core.env.ConfigurableEnvironment;
+
+/**
+ * Utility class that will initialize the DSpace Kernel on Spring startup.
+ * Adapted from the class of the same name in {@code dspace-server-webapp}.
+ */
+public class DSpaceKernelInitializer
+ implements ApplicationContextInitializer {
+
+ private static final Logger log = LogManager.getLogger();
+
+ private transient DSpaceKernel dspaceKernel;
+
+ @Override
+ public void initialize(final ConfigurableApplicationContext applicationContext) {
+ // Check if the kernel is already started
+ this.dspaceKernel = DSpaceKernelManager.getDefaultKernel();
+ if (this.dspaceKernel == null) {
+ DSpaceKernelImpl kernelImpl = null;
+ try {
+ // Load the kernel with default settings
+ kernelImpl = DSpaceKernelInit.getKernel(null);
+ if (!kernelImpl.isRunning()) {
+ // Determine configured DSpace home & init the Kernel
+ kernelImpl.start(getDSpaceHome(applicationContext.getEnvironment()));
+ }
+ this.dspaceKernel = kernelImpl;
+
+ } catch (Exception e) {
+ // failed to start so destroy it and log and throw an exception
+ try {
+ if (kernelImpl != null) {
+ kernelImpl.destroy();
+ }
+ this.dspaceKernel = null;
+ } catch (Exception e1) {
+ // nothing
+ }
+ String message = "Failure during ServletContext initialisation: " + e.getMessage();
+ log.error("Failure during ServletContext initialisation: ", e);
+ throw new RuntimeException(message, e);
+ }
+ }
+
+ if (applicationContext.getParent() == null) {
+ // Set the DSpace Kernel Application context as a parent of the
+ // Spring context so that we can auto-wire all DSpace Kernel services.
+ applicationContext.setParent(dspaceKernel.getServiceManager().getApplicationContext());
+
+ // Add a listener for Spring application shutdown so that we can
+ // nicely cleanup the DSpace kernel.
+ applicationContext.addApplicationListener(new DSpaceKernelDestroyer(dspaceKernel));
+ }
+ }
+
+ /**
+ * Find DSpace's "home" directory (from current environment)
+ * Initially look for JNDI Resource called "java:/comp/env/dspace.dir".
+ * If not found, use value provided in "dspace.dir" in Spring Environment
+ */
+ private String getDSpaceHome(ConfigurableEnvironment environment) {
+ // Load the "dspace.dir" property from Spring's configuration.
+ // This gives us the location of our DSpace configuration, which is
+ // necessary to start the kernel.
+ String providedHome = environment.getProperty(DSpaceConfigurationService.DSPACE_HOME);
+
+ String dspaceHome = null;
+ try {
+ // Allow ability to override home directory via JNDI
+ Context ctx = new InitialContext();
+ dspaceHome = (String) ctx.lookup("java:/comp/env/" + DSpaceConfigurationService.DSPACE_HOME);
+ } catch (Exception e) {
+ // do nothing
+ }
+
+ // Otherwise, verify the 'providedHome' value is non-empty, exists and includes DSpace configs
+ if (dspaceHome == null) {
+ if (StringUtils.isNotBlank(providedHome) &&
+ !providedHome.equals("${" + DSpaceConfigurationService.DSPACE_HOME + "}")) {
+ File test = new File(providedHome);
+ if (test.exists() && new File(test, DSpaceConfigurationService.DSPACE_CONFIG_PATH).exists()) {
+ dspaceHome = providedHome;
+ }
+ }
+ }
+ return dspaceHome;
+ }
+
+
+ /**
+ * Utility class that will destroy the DSpace Kernel on Spring shutdown.
+ */
+ private class DSpaceKernelDestroyer
+ implements ApplicationListener {
+ private DSpaceKernel kernel;
+
+ public DSpaceKernelDestroyer(DSpaceKernel kernel) {
+ this.kernel = kernel;
+ }
+
+ @Override
+ public void onApplicationEvent(final ContextClosedEvent event) {
+ if (this.kernel != null) {
+ this.kernel.destroy();
+ this.kernel = null;
+ }
+ }
+ }
+}
+
diff --git a/dspace-api/src/test/java/org/dspace/workflow/WorkflowCurationIT.java b/dspace-api/src/test/java/org/dspace/workflow/WorkflowCurationIT.java
new file mode 100644
index 0000000000..d5109a2def
--- /dev/null
+++ b/dspace-api/src/test/java/org/dspace/workflow/WorkflowCurationIT.java
@@ -0,0 +1,115 @@
+/**
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+package org.dspace.workflow;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.util.List;
+import java.util.regex.Pattern;
+import javax.inject.Inject;
+
+import org.dspace.AbstractIntegrationTestWithDatabase;
+import org.dspace.builder.CollectionBuilder;
+import org.dspace.builder.CommunityBuilder;
+import org.dspace.builder.EPersonBuilder;
+import org.dspace.builder.WorkflowItemBuilder;
+import org.dspace.content.Collection;
+import org.dspace.content.Community;
+import org.dspace.content.MetadataValue;
+import org.dspace.content.service.ItemService;
+import org.dspace.ctask.testing.MarkerTask;
+import org.dspace.eperson.EPerson;
+import org.dspace.services.ConfigurationService;
+import org.dspace.util.DSpaceConfigurationInitializer;
+import org.dspace.util.DSpaceKernelInitializer;
+import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+
+/**
+ * Test the attachment of curation tasks to workflows.
+ *
+ * @author mwood
+ */
+@RunWith(SpringRunner.class)
+@ContextConfiguration(
+ initializers = { DSpaceKernelInitializer.class, DSpaceConfigurationInitializer.class },
+ locations = { "classpath:spring/*.xml" }
+)
+public class WorkflowCurationIT
+ extends AbstractIntegrationTestWithDatabase {
+ @Inject
+ private ConfigurationService configurationService;
+
+ @Inject
+ private ItemService itemService;
+
+ /**
+ * Basic smoke test of a curation task attached to a workflow step.
+ * See {@link MarkerTask}.
+ * @throws java.lang.Exception passed through.
+ */
+ @Test
+ public void curationTest()
+ throws Exception {
+ context.turnOffAuthorisationSystem();
+
+ //** GIVEN **
+
+ // A submitter;
+ EPerson submitter = EPersonBuilder.createEPerson(context)
+ .withEmail("submitter@example.com")
+ .withPassword(password)
+ .withLanguage("en")
+ .build();
+
+ // A containment hierarchy;
+ Community community = CommunityBuilder.createCommunity(context)
+ .withName("Community")
+ .build();
+ final String CURATION_COLLECTION_HANDLE = "123456789/curation-test-1";
+ Collection collection = CollectionBuilder
+ .createCollection(context, community, CURATION_COLLECTION_HANDLE)
+ .withName("Collection")
+ .build();
+
+ // A workflow configuration for the test Collection;
+ // See test/dspaceFolder/config/spring/api/workflow.xml
+
+ // A curation task attached to the workflow;
+ // See test/dspaceFolder/config/workflow-curation.xml for the attachment.
+ // This should include MarkerTask.
+
+ // A workflow item;
+ context.setCurrentUser(submitter);
+ XmlWorkflowItem wfi = WorkflowItemBuilder.createWorkflowItem(context, collection)
+ .withTitle("Test of workflow curation")
+ .withIssueDate("2021-05-14")
+ .withSubject("Testing")
+ .build();
+
+ context.restoreAuthSystemState();
+
+ //** THEN **
+
+ // Search the Item's provenance for MarkerTask's name.
+ List provenance = itemService.getMetadata(wfi.getItem(),
+ MarkerTask.SCHEMA, MarkerTask.ELEMENT, MarkerTask.QUALIFIER, MarkerTask.LANGUAGE);
+ Pattern markerPattern = Pattern.compile(MarkerTask.class.getCanonicalName());
+ boolean found = false;
+ for (MetadataValue record : provenance) {
+ if (markerPattern.matcher(record.getValue()).find()) {
+ found = true;
+ break;
+ }
+ }
+ assertThat("Item should have been curated", found);
+ }
+}