You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2020/11/18 11:14:53 UTC

[isis] branch master updated: ISIS-2464: make Application-Layer test code reusable for different aspects of testing

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new 97db60a  ISIS-2464: make Application-Layer test code reusable for different aspects of testing
97db60a is described below

commit 97db60abadc4df89e6ad0f289d063a08b4e38a49
Author: Andi Huber <ah...@apache.org>
AuthorDate: Wed Nov 18 12:14:41 2020 +0100

    ISIS-2464: make Application-Layer test code reusable for different
    aspects of testing
    
    introduces ApplicationLayerTestFactory, which can be used via JUnit5
    DynamicTest API
---
 .../applayer/auditing/AuditerServiceTest.java      |  86 +++++++
 .../{ => applayer}/command/CommandTest.java        |   4 +-
 .../applayer/publishing/PublisherServiceTest.java  |  88 ++++++++
 .../testdomain/auditing/AuditerServiceTest.java    | 194 ----------------
 .../publishing/PublisherServiceTest.java           | 250 ---------------------
 .../applayer/ApplicationLayerTestFactory.java      | 197 ++++++++++++++++
 .../auditing/AuditerServiceForTesting.java         |   2 +-
 .../auditing/Configuration_usingAuditing.java      |   2 +-
 .../publishing/Configuration_usingPublishing.java  |   2 +-
 .../publishing/PublisherServiceForTesting.java     |  28 ++-
 .../commons/InteractionTestAbstract.java           |   2 +-
 .../entitychangetracking/ChangedObjectsTest.java   |   6 +-
 12 files changed, 407 insertions(+), 454 deletions(-)

diff --git a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/applayer/auditing/AuditerServiceTest.java b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/applayer/auditing/AuditerServiceTest.java
new file mode 100644
index 0000000..f4f0299
--- /dev/null
+++ b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/applayer/auditing/AuditerServiceTest.java
@@ -0,0 +1,86 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.isis.testdomain.applayer.auditing;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.apache.isis.commons.collections.Can;
+import org.apache.isis.core.config.presets.IsisPresets;
+import org.apache.isis.testdomain.applayer.ApplicationLayerTestFactory;
+import org.apache.isis.testdomain.conf.Configuration_usingJdo;
+import org.apache.isis.testdomain.util.CollectionAssertions;
+import org.apache.isis.testdomain.util.kv.KVStoreForTesting;
+import org.apache.isis.testing.integtestsupport.applib.IsisIntegrationTestAbstract;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.TestFactory;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.TestPropertySource;
+
+import lombok.val;
+
+@SpringBootTest(
+        classes = {
+                Configuration_usingJdo.class,
+                Configuration_usingAuditing.class,
+                ApplicationLayerTestFactory.class
+        }, 
+        properties = {
+                "logging.level.org.apache.isis.testdomain.util.rest.KVStoreForTesting=DEBUG"
+        })
+@TestPropertySource({
+    IsisPresets.UseLog4j2Test
+})
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+class AuditerServiceTest extends IsisIntegrationTestAbstract {
+
+    @Inject private ApplicationLayerTestFactory testFactory;
+    @Inject private KVStoreForTesting kvStore;
+    
+    @TestFactory @DisplayName("Application Layer")
+    List<DynamicTest> generateTests() {
+    	return testFactory.generateTests(this::given, this::thenHappyCase, this::thenFailureCase);
+    }
+
+    private void given() {
+    	AuditerServiceForTesting.clearAuditEntries(kvStore);
+    }
+    
+    private void thenHappyCase() {
+    	assertHasAuditEntries(Can.of(
+              "Jdo Book/name: 'Sample Book' -> 'Book #2'"));
+    }
+
+    private void thenFailureCase() {
+    	assertHasAuditEntries(Can.empty());
+    }
+
+    // -- HELPER
+
+    private void assertHasAuditEntries(Can<String> expectedAuditEntries) {
+        val actualAuditEntries = AuditerServiceForTesting.getAuditEntries(kvStore);
+        CollectionAssertions.assertComponentWiseEquals(expectedAuditEntries, actualAuditEntries);
+    }
+
+
+}
diff --git a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/command/CommandTest.java b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/applayer/command/CommandTest.java
similarity index 96%
rename from regressiontests/incubating/src/test/java/org/apache/isis/testdomain/command/CommandTest.java
rename to regressiontests/incubating/src/test/java/org/apache/isis/testdomain/applayer/command/CommandTest.java
index 1ce295f..41cea22 100644
--- a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/command/CommandTest.java
+++ b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/applayer/command/CommandTest.java
@@ -16,7 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.testdomain.command;
+package org.apache.isis.testdomain.applayer.command;
 
 import javax.inject.Inject;
 
@@ -32,7 +32,7 @@ import org.apache.isis.applib.services.factory.FactoryService;
 import org.apache.isis.applib.services.repository.RepositoryService;
 import org.apache.isis.applib.services.wrapper.WrapperFactory;
 import org.apache.isis.core.config.presets.IsisPresets;
-import org.apache.isis.testdomain.auditing.Configuration_usingAuditing;
+import org.apache.isis.testdomain.applayer.auditing.Configuration_usingAuditing;
 import org.apache.isis.testdomain.conf.Configuration_usingJdo;
 import org.apache.isis.testdomain.jdo.JdoInventoryManager;
 import org.apache.isis.testdomain.jdo.JdoTestDomainPersona;
diff --git a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/applayer/publishing/PublisherServiceTest.java b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/applayer/publishing/PublisherServiceTest.java
new file mode 100644
index 0000000..ea1d963
--- /dev/null
+++ b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/applayer/publishing/PublisherServiceTest.java
@@ -0,0 +1,88 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.isis.testdomain.applayer.publishing;
+
+import static org.apache.isis.testdomain.applayer.publishing.PublisherServiceForTesting.clearPublishedEntries;
+import static org.apache.isis.testdomain.applayer.publishing.PublisherServiceForTesting.getCreated;
+import static org.apache.isis.testdomain.applayer.publishing.PublisherServiceForTesting.getDeleted;
+import static org.apache.isis.testdomain.applayer.publishing.PublisherServiceForTesting.getLoaded;
+import static org.apache.isis.testdomain.applayer.publishing.PublisherServiceForTesting.getModified;
+import static org.apache.isis.testdomain.applayer.publishing.PublisherServiceForTesting.getUpdated;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.apache.isis.core.config.presets.IsisPresets;
+import org.apache.isis.testdomain.applayer.ApplicationLayerTestFactory;
+import org.apache.isis.testdomain.conf.Configuration_usingJdo;
+import org.apache.isis.testdomain.util.kv.KVStoreForTesting;
+import org.apache.isis.testing.integtestsupport.applib.IsisIntegrationTestAbstract;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.TestFactory;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.TestPropertySource;
+
+@SpringBootTest(
+        classes = {
+                Configuration_usingJdo.class,
+                Configuration_usingPublishing.class,
+                ApplicationLayerTestFactory.class
+        }, 
+        properties = {
+                "logging.level.org.apache.isis.persistence.jdo.datanucleus5.persistence.IsisTransactionJdo=DEBUG",
+                "logging.level.org.apache.isis.core.runtimeservices.session.IsisInteractionFactoryDefault=DEBUG",
+                "logging.level.org.apache.isis.persistence.jdo.datanucleus5.datanucleus.service.JdoPersistenceLifecycleService=DEBUG"
+        })
+@TestPropertySource({
+    IsisPresets.UseLog4j2Test
+})
+class PublisherServiceTest extends IsisIntegrationTestAbstract {
+
+	@Inject private ApplicationLayerTestFactory testFactory;
+    @Inject private KVStoreForTesting kvStore;
+    
+    @TestFactory @DisplayName("Application Layer")
+    List<DynamicTest> generateTests() {
+    	return testFactory.generateTests(this::given, this::thenHappyCase, this::thenFailureCase);
+    }
+
+    private void given() {
+        clearPublishedEntries(kvStore);
+    }
+    
+    private void thenHappyCase() {
+        assertEquals(0, getCreated(kvStore));
+        assertEquals(0, getDeleted(kvStore));
+        //assertEquals(1, getLoaded()); // not reproducible
+        assertEquals(1, getUpdated(kvStore));
+        assertEquals(1, getModified(kvStore));
+    }
+
+    private void thenFailureCase() {
+        assertEquals(0, getCreated(kvStore));
+        assertEquals(0, getDeleted(kvStore));
+        assertEquals(0, getLoaded(kvStore));
+        assertEquals(0, getUpdated(kvStore));
+        assertEquals(0, getModified(kvStore));
+    }
+
+}
diff --git a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/auditing/AuditerServiceTest.java b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/auditing/AuditerServiceTest.java
deleted file mode 100644
index 484e0f1..0000000
--- a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/auditing/AuditerServiceTest.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.apache.isis.testdomain.auditing;
-
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import javax.inject.Inject;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.MethodOrderer;
-import org.junit.jupiter.api.Order;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestMethodOrder;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.TestPropertySource;
-import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.support.TransactionTemplate;
-
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import org.apache.isis.applib.services.repository.RepositoryService;
-import org.apache.isis.applib.services.wrapper.DisabledException;
-import org.apache.isis.applib.services.wrapper.WrapperFactory;
-import org.apache.isis.applib.services.wrapper.control.AsyncControl;
-import org.apache.isis.applib.services.wrapper.control.SyncControl;
-import org.apache.isis.commons.collections.Can;
-import org.apache.isis.commons.internal.debug._Probe;
-import org.apache.isis.core.config.presets.IsisPresets;
-import org.apache.isis.testdomain.auditing.AuditerServiceForTesting;
-import org.apache.isis.testdomain.auditing.Configuration_usingAuditing;
-import org.apache.isis.testdomain.conf.Configuration_usingJdo;
-import org.apache.isis.testdomain.jdo.JdoTestDomainPersona;
-import org.apache.isis.testdomain.jdo.entities.JdoBook;
-import org.apache.isis.testdomain.util.CollectionAssertions;
-import org.apache.isis.testdomain.util.kv.KVStoreForTesting;
-import org.apache.isis.testing.fixtures.applib.fixturescripts.FixtureScripts;
-import org.apache.isis.testing.integtestsupport.applib.IsisIntegrationTestAbstract;
-
-import static org.apache.isis.applib.services.wrapper.control.AsyncControl.returningVoid;
-
-import lombok.val;
-
-@SpringBootTest(
-        classes = {
-                Configuration_usingJdo.class,
-                Configuration_usingAuditing.class
-        }, 
-        properties = {
-                "logging.level.org.apache.isis.testdomain.util.rest.KVStoreForTesting=DEBUG"
-        })
-@TestPropertySource({
-    IsisPresets.UseLog4j2Test
-})
-@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
-class AuditerServiceTest extends IsisIntegrationTestAbstract {
-
-    @Inject private RepositoryService repository;
-    @Inject private FixtureScripts fixtureScripts;
-    @Inject private WrapperFactory wrapper;
-    @Inject private KVStoreForTesting kvStore;
-    @Inject private PlatformTransactionManager txMan;
-
-    @BeforeEach
-    void setUp() {
-        // cleanup
-        fixtureScripts.runPersona(JdoTestDomainPersona.PurgeAll);
-
-        // given
-        fixtureScripts.runPersona(JdoTestDomainPersona.InventoryWith1Book);
-    }
-
-    @Test @Order(1)
-    void auditerService_shouldBeAwareOfInventoryChanges() {
-
-        // given
-        val book = repository.allInstances(JdoBook.class).listIterator().next();
-        AuditerServiceForTesting.clearAuditEntries(kvStore);
-
-        _Probe.errOut("(1) BEFORE BOOK UPDATED");
-
-        // when - running within its own transactional boundary
-        val transactionTemplate = new TransactionTemplate(txMan);
-        transactionTemplate.execute(status -> {
-
-            book.setName("Book #2");
-            repository.persist(book);
-
-            // then - before the commit
-            assertFalse(kvStore.get(AuditerServiceForTesting.class, "audit").isPresent());
-
-            return null;
-        });
-
-        _Probe.errOut("(1) AFTER BOOK UPDATED");
-
-        // then - after the commit
-        assertHasAuditEntries(Can.of(
-                "Jdo Book/name: 'Sample Book' -> 'Book #2'"));
-    }
-
-    @Test @Order(2)
-    void auditerService_shouldBeAwareOfInventoryChanges_whenUsingSyncExecution() throws ExecutionException, InterruptedException, TimeoutException {
-
-        // given
-        val book = repository.allInstances(JdoBook.class).listIterator().next();
-        AuditerServiceForTesting.clearAuditEntries(kvStore);
-
-        _Probe.errOut("(2) BEFORE BOOK UPDATED");
-
-        // when - running synchronous
-        val control = SyncControl.control().withSkipRules();
-        wrapper.wrap(book, control).setName("Book #2");
-
-        _Probe.errOut("(2) AFTER BOOK UPDATED");
-
-        // then - after the commit
-        assertHasAuditEntries(Can.of(
-                "Jdo Book/name: 'Sample Book' -> 'Book #2'"));
-    }
-
-
-    @Test @Order(3)
-    void auditerService_shouldBeAwareOfInventoryChanges_whenUsingAsyncExecution() throws ExecutionException, InterruptedException, TimeoutException {
-
-        // given
-        val book = repository.allInstances(JdoBook.class).listIterator().next();
-        AuditerServiceForTesting.clearAuditEntries(kvStore);
-        val latch = kvStore.latch(AuditerServiceForTesting.class);
-
-        _Probe.errOut("(3) BEFORE BOOK UPDATED");
-
-        // when - running within its own background task
-        AsyncControl<Void> control = returningVoid().withSkipRules();
-        wrapper.asyncWrap(book, control).setName("Book #2");
-
-        control.getFuture().get(10, TimeUnit.SECONDS); // blocks the current thread
-
-        latch.await(2, TimeUnit.SECONDS);
-
-        _Probe.errOut("(3) AFTER BOOK UPDATED");
-
-        // then - after the commit
-        assertHasAuditEntries(Can.of(
-                "Jdo Book/name: 'Sample Book' -> 'Book #2'"));
-    }
-
-    @Test @Order(4)
-    void auditerService_shouldNotBeAwareOfInventoryChanges_whenUsingAsyncExecutionThatFails() {
-
-        // given
-        val book = repository.allInstances(JdoBook.class).listIterator().next();
-        AuditerServiceForTesting.clearAuditEntries(kvStore);
-
-        // when - running within its own background task
-        assertThrows(DisabledException.class, ()->{
-
-            wrapper.asyncWrap(book, returningVoid()).setName("Book #2");
-
-            returningVoid().getFuture().get(1000, TimeUnit.SECONDS);
-
-        });
-
-        // then - after the exception
-        assertHasAuditEntries(Can.empty());
-    }
-
-    // -- HELPER
-
-    private void assertHasAuditEntries(Can<String> expectedAuditEntries) {
-        val actualAuditEntries = AuditerServiceForTesting.getAuditEntries(kvStore);
-        CollectionAssertions.assertComponentWiseEquals(expectedAuditEntries, actualAuditEntries);
-    }
-
-
-}
diff --git a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/PublisherServiceTest.java b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/PublisherServiceTest.java
deleted file mode 100644
index 7eeef99..0000000
--- a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/PublisherServiceTest.java
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.apache.isis.testdomain.publishing;
-
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import javax.inject.Inject;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.MethodOrderer;
-import org.junit.jupiter.api.Order;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestMethodOrder;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.TestPropertySource;
-import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.support.TransactionTemplate;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import org.apache.isis.applib.services.publish.PublishedObjects;
-import org.apache.isis.applib.services.repository.RepositoryService;
-import org.apache.isis.applib.services.wrapper.DisabledException;
-import org.apache.isis.applib.services.wrapper.WrapperFactory;
-import org.apache.isis.applib.services.wrapper.control.AsyncControl;
-import org.apache.isis.applib.services.wrapper.control.SyncControl;
-import org.apache.isis.applib.services.xactn.TransactionService;
-import org.apache.isis.applib.services.xactn.TransactionState;
-import org.apache.isis.commons.internal.debug._Probe;
-import org.apache.isis.core.config.presets.IsisPresets;
-import org.apache.isis.testdomain.conf.Configuration_usingJdo;
-import org.apache.isis.testdomain.jdo.JdoTestDomainPersona;
-import org.apache.isis.testdomain.jdo.entities.JdoBook;
-import org.apache.isis.testdomain.util.kv.KVStoreForTesting;
-import org.apache.isis.testing.fixtures.applib.fixturescripts.FixtureScripts;
-import org.apache.isis.testing.integtestsupport.applib.IsisIntegrationTestAbstract;
-
-import static org.apache.isis.applib.services.wrapper.control.AsyncControl.returningVoid;
-
-import lombok.val;
-
-@SpringBootTest(
-        classes = {
-                Configuration_usingJdo.class,
-                Configuration_usingPublishing.class
-        }, 
-        properties = {
-                "logging.level.org.apache.isis.persistence.jdo.datanucleus5.persistence.IsisTransactionJdo=DEBUG",
-                "logging.level.org.apache.isis.core.runtimeservices.session.IsisInteractionFactoryDefault=DEBUG",
-                "logging.level.org.apache.isis.persistence.jdo.datanucleus5.datanucleus.service.JdoPersistenceLifecycleService=DEBUG"
-        })
-@TestPropertySource({
-    IsisPresets.UseLog4j2Test
-})
-@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
-class PublisherServiceTest extends IsisIntegrationTestAbstract {
-
-    @Inject private RepositoryService repository;
-    @Inject private FixtureScripts fixtureScripts;
-    @Inject private WrapperFactory wrapper;
-    @Inject private PlatformTransactionManager txMan; 
-    @Inject private KVStoreForTesting kvStore;
-    @Inject private TransactionService transactionService;
-    
-    @BeforeEach
-    void setUp() {
-
-        // cleanup
-        fixtureScripts.runPersona(JdoTestDomainPersona.PurgeAll);
-
-        // given
-        fixtureScripts.runPersona(JdoTestDomainPersona.InventoryWith1Book);
-    }
-
-    @Test @Order(0)
-    void no_initial_tx_context() {
-        val txState = transactionService.currentTransactionState();
-        assertEquals(TransactionState.NONE, txState);
-    }
-    
-    @Test @Order(1)
-    void publisherService_shouldBeAwareOfInventoryChanges() {
-
-        // given
-        val book = repository.allInstances(JdoBook.class).listIterator().next();
-        PublisherServiceForTesting.clearPublishedEntries(kvStore);
-        
-        _Probe.errOut("(1) BEFORE BOOK UPDATED");
-        
-        // when - running within its own transactional boundary
-        val transactionTemplate = new TransactionTemplate(txMan);
-        transactionTemplate.execute(status -> {
-
-            book.setName("Book #2");
-            repository.persist(book);
-
-            // then - before the commit
-            assertEquals(0, kvStore.countEntries(PublisherServiceForTesting.class));
-            
-            return null;
-        });
-        
-        _Probe.errOut("(1) AFTER BOOK UPDATED");
-        
-        // this test does not trigger publishing 
-        // because invocation happens directly rather than through the means of
-        // an implementation of ObjectMemberAbstract, which has the meta-data
-        // required to generate the associated CommandDTO
-        
-        //TODO however, metrics as tested below are still available even though Commands don't have CommandDTOs
-        
-        // then - after the commit
-        assertEquals(0, getCreated());
-        assertEquals(0, getDeleted());
-        //assertEquals(1, getLoaded()); // not reproducible
-        //TODO assertEquals(1, getUpdated());
-        //TODO assertEquals(1, getModified());
-    }
-    
-    @Test @Order(2)
-    void publisherService_shouldBeAwareOfInventoryChanges_whenUsingSyncExecution() 
-            throws InterruptedException, ExecutionException, TimeoutException {
-
-        // given
-        val book = repository.allInstances(JdoBook.class).listIterator().next();
-        PublisherServiceForTesting.clearPublishedEntries(kvStore);
-
-        _Probe.errOut("(2) BEFORE BOOK UPDATED");
-        
-        // when - running synchronous
-        val syncControl = SyncControl.control().withSkipRules();
-        wrapper.wrap(book, syncControl).setName("Book #2"); // don't enforce rules for this test
-
-        _Probe.errOut("(2) AFTER BOOK UPDATED");
-
-        
-        // then - after the commit
-        assertEquals(0, getCreated());
-        assertEquals(0, getDeleted());
-        //assertEquals(1, getLoaded()); // not reproducible
-        assertEquals(1, getUpdated());
-        assertEquals(1, getModified());
-
-    }
-
-    @Test @Order(3)
-    void publisherService_shouldBeAwareOfInventoryChanges_whenUsingAsyncExecution() 
-            throws InterruptedException, ExecutionException, TimeoutException {
-
-        // given
-        val book = repository.allInstances(JdoBook.class).listIterator().next();
-        PublisherServiceForTesting.clearPublishedEntries(kvStore);
-        val latch = kvStore.latch(PublisherServiceForTesting.class);
-
-        _Probe.errOut("(3) BEFORE BOOK UPDATED");
-        
-        // when - running within its own background task
-        AsyncControl<Void> control = returningVoid().withSkipRules();
-        wrapper.asyncWrap(book, control).setName("Book #2"); // don't enforce rules for this test
-
-        control.getFuture().get(10, TimeUnit.SECONDS);
-
-        latch.await(2, TimeUnit.SECONDS);
-        
-        _Probe.errOut("(3) AFTER BOOK UPDATED");
-
-        // then - after the commit
-        assertEquals(0, getCreated());
-        assertEquals(0, getDeleted());
-        //assertEquals(1, getLoaded()); // not reproducible
-        assertEquals(1, getUpdated());
-        assertEquals(1, getModified());
-
-    }
-    
-    
-    @Test @Order(4)
-    void publisherService_shouldNotBeAwareOfInventoryChanges_whenUsingAsyncExecutionFails() 
-            throws InterruptedException, ExecutionException, TimeoutException {
-
-        // given
-        val book = repository.allInstances(JdoBook.class).listIterator().next();
-        PublisherServiceForTesting.clearPublishedEntries(kvStore);
-
-        // when - running within its own background task
-        assertThrows(DisabledException.class, ()->{
-
-            wrapper.asyncWrap(book, returningVoid()).setName("Book #2");
-            
-            returningVoid().getFuture().get(10, TimeUnit.SECONDS);
-            
-        });
-
-        // then - after the commit
-        assertEquals(0, getCreated());
-        assertEquals(0, getDeleted());
-        assertEquals(0, getLoaded());
-        assertEquals(0, getUpdated());
-        assertEquals(0, getModified());
-
-    }
-
-    // -- HELPER
-    
-    private int getCreated() {
-        val publishedObjects = PublisherServiceForTesting.getPublishedObjects(kvStore);
-        return publishedObjects.stream().mapToInt(PublishedObjects::getNumberCreated).sum();
-    }
-    
-    private int getDeleted() {
-        val publishedObjects = PublisherServiceForTesting.getPublishedObjects(kvStore);
-        return publishedObjects.stream().mapToInt(PublishedObjects::getNumberDeleted).sum();
-    }
-    
-    private int getLoaded() {
-        val publishedObjects = PublisherServiceForTesting.getPublishedObjects(kvStore);
-        return publishedObjects.stream().mapToInt(PublishedObjects::getNumberLoaded).sum();
-    }
-    
-    private int getUpdated() {
-        val publishedObjects = PublisherServiceForTesting.getPublishedObjects(kvStore);
-        return publishedObjects.stream().mapToInt(PublishedObjects::getNumberUpdated).sum();
-    }
-    
-    private int getModified() {
-        val publishedObjects = PublisherServiceForTesting.getPublishedObjects(kvStore);
-        return publishedObjects.stream().mapToInt(PublishedObjects::getNumberPropertiesModified).sum();
-    }
-
-
-}
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/applayer/ApplicationLayerTestFactory.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/applayer/ApplicationLayerTestFactory.java
new file mode 100644
index 0000000..899019b
--- /dev/null
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/applayer/ApplicationLayerTestFactory.java
@@ -0,0 +1,197 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.isis.testdomain.applayer;
+
+import static org.apache.isis.applib.services.wrapper.control.AsyncControl.returningVoid;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.DynamicTest.dynamicTest;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+
+import org.apache.isis.applib.services.repository.RepositoryService;
+import org.apache.isis.applib.services.wrapper.DisabledException;
+import org.apache.isis.applib.services.wrapper.WrapperFactory;
+import org.apache.isis.applib.services.wrapper.control.SyncControl;
+import org.apache.isis.applib.services.xactn.TransactionService;
+import org.apache.isis.applib.services.xactn.TransactionState;
+import org.apache.isis.commons.internal.collections._Lists;
+import org.apache.isis.testdomain.jdo.JdoTestDomainPersona;
+import org.apache.isis.testdomain.jdo.entities.JdoBook;
+import org.apache.isis.testing.fixtures.applib.fixturescripts.FixtureScripts;
+import org.junit.jupiter.api.DynamicTest;
+import org.springframework.stereotype.Component;
+
+import lombok.RequiredArgsConstructor;
+import lombok.val;
+
+@Component
+@RequiredArgsConstructor(onConstructor_ = {@Inject})
+public class ApplicationLayerTestFactory {
+	
+	private final RepositoryService repository;
+	private final WrapperFactory wrapper;
+	private final TransactionService transactionService;
+	private final FixtureScripts fixtureScripts;
+
+	public List<DynamicTest> generateTests(
+			final Runnable given,
+			final Runnable thenHappyCase,
+			final Runnable thenFailureCase) {
+		return _Lists.of(
+				dynamicTest("No initial Transaction with Test Execution", this::no_initial_tx_context),
+				programmaticExecution(given, thenFailureCase),
+				wrapperSyncExecution(given, thenHappyCase),
+				wrapperSyncExecutionWithFailure(given, thenFailureCase),
+				wrapperAsyncExecution(given, thenHappyCase),
+				wrapperAsyncExecutionWithFailure(given, thenFailureCase)
+				);
+	}
+	
+	// -- TEST SETUP
+	
+	private JdoBook setupForJdo() {
+        // cleanup
+        fixtureScripts.runPersona(JdoTestDomainPersona.PurgeAll);
+        // given
+        fixtureScripts.runPersona(JdoTestDomainPersona.InventoryWith1Book);
+        
+        return repository.allInstances(JdoBook.class).listIterator().next();
+	}
+	
+	// -- TESTS - ENSURE TESTS ARE CORRECTLY INVOKED 
+	
+    void no_initial_tx_context() {
+        val txState = transactionService.currentTransactionState();
+        assertEquals(TransactionState.NONE, txState);
+    }
+	
+	// -- TESTS - WRAPPER SYNC
+	
+	private DynamicTest programmaticExecution(
+			final Runnable given,
+			final Runnable then) {
+		return dynamicTest("Programmatic Execution", ()->{
+			// given
+	        val book = setupForJdo();
+	        given.run();
+	        
+	        // when - direct change (circumventing the framework)
+	        book.setName("Book #2");
+	        repository.persist(book);
+	        
+	        // this test does not trigger publishing 
+	        // because invocation happens directly rather than through the means of
+	        // an implementation of ObjectMemberAbstract, which has the meta-data
+	        // required to generate the associated CommandDTO
+	        
+	        //TODO however, we should still be able to receive valid metrics 
+	        // even though Commands don't have CommandDTOs
+	        
+	        // then
+	        then.run();
+		});
+	}
+	
+	// -- TESTS - WRAPPER SYNC
+	
+	private DynamicTest wrapperSyncExecution(
+			final Runnable given,
+			final Runnable then) {
+		return dynamicTest("Wrapper Sync Execution w/o Rules", ()->{
+			// given
+	        val book = setupForJdo();
+	        given.run();
+	        
+	        // when - running synchronous
+	        val syncControl = SyncControl.control().withSkipRules(); // don't enforce rules
+	        wrapper.wrap(book, syncControl).setName("Book #2"); 
+	        
+	        // then
+	        then.run();
+		});
+	}
+
+	private DynamicTest wrapperSyncExecutionWithFailure(
+			final Runnable given,
+			final Runnable then) {
+		return dynamicTest("Wrapper Sync Execution w/ Rules (expected to fail w/ DisabledException)", ()->{
+			// given
+			val book = setupForJdo();
+	        given.run();
+	        
+	        // when - running synchronous
+	        val syncControl = SyncControl.control().withCheckRules(); // enforce rules 
+	        
+	        assertThrows(DisabledException.class, ()->{
+	        	wrapper.wrap(book, syncControl).setName("Book #2"); // should fail with DisabledException
+	        });
+	        
+	        // then
+	        then.run();
+		});
+	}
+	
+	// -- TESTS - WRAPPER ASYNC
+	
+	private DynamicTest wrapperAsyncExecution(
+			final Runnable given,
+			final Runnable then) {
+		return dynamicTest("Wrapper Async Execution w/o Rules", ()->{
+			// given
+			val book = setupForJdo();
+	        given.run();
+	        
+	        // when - running asynchronous
+	        val asyncControl = returningVoid().withSkipRules(); // don't enforce rules
+	        wrapper.asyncWrap(book, asyncControl).setName("Book #2");
+	        
+	        asyncControl.getFuture().get(10, TimeUnit.SECONDS);
+	        
+	        // then
+	        then.run();
+		});
+	}
+
+	private DynamicTest wrapperAsyncExecutionWithFailure(
+			final Runnable given,
+			final Runnable then) {
+		return dynamicTest("Wrapper Async Execution w/ Rules (expected to fail w/ DisabledException)", ()->{
+			// given
+			val book = setupForJdo();
+	        given.run();
+	        
+	        // when - running synchronous
+	        val asyncControl = returningVoid().withCheckRules(); // enforce rules 
+	        
+	        assertThrows(DisabledException.class, ()->{
+	        	wrapper.asyncWrap(book, asyncControl).setName("Book #2"); // should fail with DisabledException
+		        
+		        asyncControl.getFuture().get(10, TimeUnit.SECONDS);
+	        });
+	        
+	        // then
+	        then.run();
+		});
+	}
+	
+}
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/auditing/AuditerServiceForTesting.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/applayer/auditing/AuditerServiceForTesting.java
similarity index 98%
rename from regressiontests/stable/src/main/java/org/apache/isis/testdomain/auditing/AuditerServiceForTesting.java
rename to regressiontests/stable/src/main/java/org/apache/isis/testdomain/applayer/auditing/AuditerServiceForTesting.java
index a780905..6696ad5 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/auditing/AuditerServiceForTesting.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/applayer/auditing/AuditerServiceForTesting.java
@@ -16,7 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.testdomain.auditing;
+package org.apache.isis.testdomain.applayer.auditing;
 
 import java.sql.Timestamp;
 import java.util.ArrayList;
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/auditing/Configuration_usingAuditing.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/applayer/auditing/Configuration_usingAuditing.java
similarity index 95%
rename from regressiontests/stable/src/main/java/org/apache/isis/testdomain/auditing/Configuration_usingAuditing.java
rename to regressiontests/stable/src/main/java/org/apache/isis/testdomain/applayer/auditing/Configuration_usingAuditing.java
index 38b9f7c..bc9579c 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/auditing/Configuration_usingAuditing.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/applayer/auditing/Configuration_usingAuditing.java
@@ -16,7 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.testdomain.auditing;
+package org.apache.isis.testdomain.applayer.auditing;
 
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/Configuration_usingPublishing.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/applayer/publishing/Configuration_usingPublishing.java
similarity index 95%
rename from regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/Configuration_usingPublishing.java
rename to regressiontests/stable/src/main/java/org/apache/isis/testdomain/applayer/publishing/Configuration_usingPublishing.java
index 3e01444..20b0018 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/Configuration_usingPublishing.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/applayer/publishing/Configuration_usingPublishing.java
@@ -16,7 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.testdomain.publishing;
+package org.apache.isis.testdomain.applayer.publishing;
 
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublisherServiceForTesting.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/applayer/publishing/PublisherServiceForTesting.java
similarity index 74%
rename from regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublisherServiceForTesting.java
rename to regressiontests/stable/src/main/java/org/apache/isis/testdomain/applayer/publishing/PublisherServiceForTesting.java
index ec900d6..3225f79 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublisherServiceForTesting.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/applayer/publishing/PublisherServiceForTesting.java
@@ -16,7 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.testdomain.publishing;
+package org.apache.isis.testdomain.applayer.publishing;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -95,4 +95,30 @@ public class PublisherServiceForTesting implements PublisherService {
         kvStore.clear(PublisherServiceForTesting.class);
     }
     
+    public static int getCreated(KVStoreForTesting kvStore) {
+        val publishedObjects = getPublishedObjects(kvStore);
+        return publishedObjects.stream().mapToInt(PublishedObjects::getNumberCreated).sum();
+    }
+    
+    public static int getDeleted(KVStoreForTesting kvStore) {
+        val publishedObjects = getPublishedObjects(kvStore);
+        return publishedObjects.stream().mapToInt(PublishedObjects::getNumberDeleted).sum();
+    }
+    
+    public static int getLoaded(KVStoreForTesting kvStore) {
+        val publishedObjects = getPublishedObjects(kvStore);
+        return publishedObjects.stream().mapToInt(PublishedObjects::getNumberLoaded).sum();
+    }
+    
+    public static int getUpdated(KVStoreForTesting kvStore) {
+        val publishedObjects = getPublishedObjects(kvStore);
+        return publishedObjects.stream().mapToInt(PublishedObjects::getNumberUpdated).sum();
+    }
+    
+    public static int getModified(KVStoreForTesting kvStore) {
+        val publishedObjects = getPublishedObjects(kvStore);
+        return publishedObjects.stream().mapToInt(PublishedObjects::getNumberPropertiesModified).sum();
+    }
+    
+    
 }
\ No newline at end of file
diff --git a/regressiontests/stable/src/test/java/org/apache/isis/testdomain/commons/InteractionTestAbstract.java b/regressiontests/stable/src/test/java/org/apache/isis/testdomain/commons/InteractionTestAbstract.java
index dbdcf5c..00334b8 100644
--- a/regressiontests/stable/src/test/java/org/apache/isis/testdomain/commons/InteractionTestAbstract.java
+++ b/regressiontests/stable/src/test/java/org/apache/isis/testdomain/commons/InteractionTestAbstract.java
@@ -43,7 +43,7 @@ import org.apache.isis.core.metamodel.interactions.managed.PropertyInteraction;
 import org.apache.isis.core.metamodel.objectmanager.ObjectManager;
 import org.apache.isis.core.runtime.iactn.IsisInteractionFactory;
 import org.apache.isis.core.runtime.persistence.changetracking.ChangedObjectsService;
-import org.apache.isis.testdomain.auditing.AuditerServiceForTesting;
+import org.apache.isis.testdomain.applayer.auditing.AuditerServiceForTesting;
 import org.apache.isis.testdomain.util.CollectionAssertions;
 import org.apache.isis.testdomain.util.kv.KVStoreForTesting;
 import org.apache.isis.testing.integtestsupport.applib.IsisIntegrationTestAbstract;
diff --git a/regressiontests/stable/src/test/java/org/apache/isis/testdomain/entitychangetracking/ChangedObjectsTest.java b/regressiontests/stable/src/test/java/org/apache/isis/testdomain/entitychangetracking/ChangedObjectsTest.java
index a6a780c..a62fe63 100644
--- a/regressiontests/stable/src/test/java/org/apache/isis/testdomain/entitychangetracking/ChangedObjectsTest.java
+++ b/regressiontests/stable/src/test/java/org/apache/isis/testdomain/entitychangetracking/ChangedObjectsTest.java
@@ -30,8 +30,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import org.apache.isis.commons.internal.collections._Lists;
 import org.apache.isis.core.config.presets.IsisPresets;
-import org.apache.isis.testdomain.auditing.AuditerServiceForTesting;
-import org.apache.isis.testdomain.auditing.Configuration_usingAuditing;
+import org.apache.isis.testdomain.applayer.auditing.AuditerServiceForTesting;
+import org.apache.isis.testdomain.applayer.auditing.Configuration_usingAuditing;
+import org.apache.isis.testdomain.applayer.publishing.Configuration_usingPublishing;
 import org.apache.isis.testdomain.commons.InteractionBoundaryProbe;
 import org.apache.isis.testdomain.commons.InteractionTestAbstract;
 import org.apache.isis.testdomain.conf.Configuration_usingJdo;
@@ -39,7 +40,6 @@ import org.apache.isis.testdomain.jdo.JdoInventoryManager;
 import org.apache.isis.testdomain.jdo.JdoTestDomainPersona;
 import org.apache.isis.testdomain.jdo.entities.JdoBook;
 import org.apache.isis.testdomain.jdo.entities.JdoProduct;
-import org.apache.isis.testdomain.publishing.Configuration_usingPublishing;
 import org.apache.isis.testing.fixtures.applib.fixturescripts.FixtureScripts;
 
 import lombok.val;