You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2013/07/12 18:13:10 UTC

[1/2] git commit: ISIS-463: IntegrationTestAbstract pick ScenarioExecution...

Updated Branches:
  refs/heads/master 593f500bc -> 68136cc7c


ISIS-463: IntegrationTestAbstract pick ScenarioExecution...

... from the ThreadLocal, rather than store as a (static) field.


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/5760f554
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/5760f554
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/5760f554

Branch: refs/heads/master
Commit: 5760f554700b0d8baf367f22a1ef81f59443e413
Parents: 593f500
Author: Dan Haywood <da...@apache.org>
Authored: Fri Jul 12 14:23:36 2013 +0100
Committer: Dan Haywood <da...@apache.org>
Committed: Fri Jul 12 14:23:36 2013 +0100

----------------------------------------------------------------------
 .../isis/core/integtestsupport/IntegrationTestAbstract.java   | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/5760f554/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IntegrationTestAbstract.java
----------------------------------------------------------------------
diff --git a/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IntegrationTestAbstract.java b/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IntegrationTestAbstract.java
index e4f9d55..938dd17 100644
--- a/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IntegrationTestAbstract.java
+++ b/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IntegrationTestAbstract.java
@@ -42,7 +42,9 @@ import org.apache.isis.core.unittestsupport.jmocking.JUnitRuleMockery2.Mode;
  */
 public abstract class IntegrationTestAbstract {
 
-    protected static ScenarioExecution scenarioExecution;
+    protected static ScenarioExecution scenarioExecution() {
+        return ScenarioExecution.current();
+    }
 
     // //////////////////////////////////////
 
@@ -104,9 +106,6 @@ public abstract class IntegrationTestAbstract {
     }
 
 
-    private ScenarioExecution scenarioExecution() {
-        return scenarioExecution;
-    }
     
     // //////////////////////////////////////
 


[2/2] git commit: ISIS-463: refactoring to allow specs to run under unit scope (with mocks)

Posted by da...@apache.org.
ISIS-463: refactoring to allow specs to run under unit scope (with mocks)


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/68136cc7
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/68136cc7
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/68136cc7

Branch: refs/heads/master
Commit: 68136cc7c188a8bb9e7dceecf07cf46fb0c9e3dd
Parents: 5760f55
Author: Dan Haywood <da...@apache.org>
Authored: Fri Jul 12 17:07:58 2013 +0100
Committer: Dan Haywood <da...@apache.org>
Committed: Fri Jul 12 17:07:58 2013 +0100

----------------------------------------------------------------------
 core/specsupport/pom.xml                        |   8 +
 .../core/specsupport/scenarios/InMemoryDB.java  | 174 +++++++++++++++++++
 .../scenarios/ScenarioExecution.java            |  89 +++++++++-
 .../scenarios/ScenarioExecutionForUnit.java     | 131 ++++++++++++++
 .../scenarios/ScenarioExecutionScope.java       |  13 +-
 .../specsupport/specs/CukeStepDefsAbstract.java |  51 +++++-
 6 files changed, 455 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/68136cc7/core/specsupport/pom.xml
----------------------------------------------------------------------
diff --git a/core/specsupport/pom.xml b/core/specsupport/pom.xml
index ea1a304..99a19da 100644
--- a/core/specsupport/pom.xml
+++ b/core/specsupport/pom.xml
@@ -70,6 +70,14 @@
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
         </dependency>
+        <dependency>
+           <groupId>org.jmock</groupId>
+           <artifactId>jmock</artifactId>
+        </dependency>
+        <dependency>
+           <groupId>org.jmock</groupId>
+           <artifactId>jmock-legacy</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>info.cukes</groupId>

http://git-wip-us.apache.org/repos/asf/isis/blob/68136cc7/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/InMemoryDB.java
----------------------------------------------------------------------
diff --git a/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/InMemoryDB.java b/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/InMemoryDB.java
new file mode 100644
index 0000000..ded1d9c
--- /dev/null
+++ b/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/InMemoryDB.java
@@ -0,0 +1,174 @@
+/**
+ *  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.core.specsupport.scenarios;
+
+import java.util.Map;
+
+import com.google.common.collect.Maps;
+
+import org.hamcrest.Description;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+
+import org.apache.isis.applib.DomainObjectContainer;
+
+/**
+ * To support unit-scope specifications, intended to work with mocks.
+ * 
+ * <p>
+ * The {@link #findByXxx(Class, Strategy)} provides an implementation of a JMock
+ * {@link Action} that can simulate searching for an object from a database, and 
+ * optionally automatically creating a new one if {@link Strategy specified}.
+ * 
+ * <p>
+ * If objects are created, then (mock) services are automatically injected.  This is performed by
+ * searching for <tt>injectXxx()</tt> methods.  The (mock) {@link DomainObjectContainer container}
+ * is also automatically injected, through the <tt>setXxx</tt> method.
+ * 
+ * <p>
+ * Finally, note that the {@link #init(Object, String) init} hook method allows subclasses to
+ * customize the state of any objects created.
+ */
+public class InMemoryDB {
+    
+    private final ScenarioExecution scenarioExecution;
+
+    public InMemoryDB(ScenarioExecution scenarioExecution) {
+        this.scenarioExecution = scenarioExecution;
+    }
+    
+    public static class EntityId {
+        private final String type;
+        private final String id;
+        public EntityId(Class<?> type, String id) {
+            this.type = type.getName();
+            this.id = id;
+        }
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((id == null) ? 0 : id.hashCode());
+            result = prime * result + ((type == null) ? 0 : type.hashCode());
+            return result;
+        }
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            InMemoryDB.EntityId other = (InMemoryDB.EntityId) obj;
+            if (id == null) {
+                if (other.id != null)
+                    return false;
+            } else if (!id.equals(other.id))
+                return false;
+            if (type == null) {
+                if (other.type != null)
+                    return false;
+            } else if (!type.equals(other.type))
+                return false;
+            return true;
+        }
+        @Override
+        public String toString() {
+            return "EntityId [type=" + type + ", id=" + id + "]";
+        }
+    }
+    
+    private Map<InMemoryDB.EntityId, Object> objectsById = Maps.newHashMap();
+    
+    private Object get(Class<?> cls, final String id) {
+        while(cls != null) {
+            // search for this class and all superclasses
+            final InMemoryDB.EntityId entityId = new EntityId(cls, id);
+            final Object object = objectsById.get(entityId);
+            if(object != null) {
+                return object;
+            }
+            cls = cls.getSuperclass();
+        }
+        return null;
+    }
+
+    private Object getElseCreate(Class<?> cls, final String id) {
+        final Object object = get(cls, id);
+        if(object != null) { 
+            return object;
+        }
+        Object obj;
+        obj = instantiateAndInject(cls);
+        init(obj, id);
+        
+        // put for this class and all superclasses
+        while(cls != null) {
+            final InMemoryDB.EntityId entityId = new EntityId(cls, id);
+            objectsById.put(entityId, obj);
+            cls = cls.getSuperclass();
+        }
+            return obj;
+    }
+
+    private Object instantiateAndInject(Class<?> cls)  {
+        try {
+            return scenarioExecution.injectServices(cls.newInstance());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public enum Strategy {
+        STRICT,
+        AUTOCREATE
+    }
+    
+    public Action findByXxx(final Class<?> cls, final InMemoryDB.Strategy strategy) {
+        return new Action() {
+            
+            @Override
+            public Object invoke(Invocation invocation) throws Throwable {
+                if(invocation.getParameterCount() != 1) {
+                    throw new IllegalArgumentException("intended for action of findByXxx");
+                }
+                final Object argObj = invocation.getParameter(0);
+                if(!(argObj instanceof String)) {
+                    throw new IllegalArgumentException("Argument must be a string");
+                } 
+                String arg = (String) argObj;
+                if(strategy == Strategy.AUTOCREATE) {
+                    return getElseCreate(cls, arg);
+                } else {
+                    return get(cls, arg);
+                }
+            }
+            
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("findByXxx for " + cls.getName());
+            }
+        };
+    }
+    
+    /**
+     * Hook to initialize if possible.
+     */
+    protected void init(Object obj, String id) {
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/68136cc7/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/ScenarioExecution.java
----------------------------------------------------------------------
diff --git a/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/ScenarioExecution.java b/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/ScenarioExecution.java
index 04cc378..741d54a 100644
--- a/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/ScenarioExecution.java
+++ b/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/ScenarioExecution.java
@@ -16,10 +16,15 @@
  */
 package org.apache.isis.core.specsupport.scenarios;
 
+import java.lang.reflect.Method;
 import java.util.Map;
 
 import com.google.common.collect.Maps;
 
+import org.jmock.Sequence;
+import org.jmock.States;
+import org.jmock.internal.ExpectationBuilder;
+
 import org.apache.isis.applib.DomainObjectContainer;
 import org.apache.isis.applib.fixtures.InstallableFixture;
 import org.apache.isis.applib.services.wrapper.WrapperFactory;
@@ -50,7 +55,7 @@ import org.apache.isis.applib.services.wrapper.WrapperFactory;
  * <tt>IntegrationScenarioExecution</tt> provides additional support for fixtures and 
  * transaction management, used both by integration-scoped specs and by integration tests.
  */
-public class ScenarioExecution {
+public abstract class ScenarioExecution {
     
     private static ThreadLocal<ScenarioExecution> current = new ThreadLocal<ScenarioExecution>();
     
@@ -67,7 +72,7 @@ public class ScenarioExecution {
 
     protected final DomainServiceProvider dsp;
     
-    public ScenarioExecution(final DomainServiceProvider dsp) {
+    protected ScenarioExecution(final DomainServiceProvider dsp) {
         this.dsp = dsp;
         current.set(this);
     }
@@ -247,11 +252,59 @@ public class ScenarioExecution {
     // //////////////////////////////////////
 
     /**
+     * Install expectations on mock domain services.
+     * 
+     * <p>
+     * This implementation is a no-op, but subclasses of this class tailored to
+     * supporting unit specs/tests are expected to override.
+     */
+    public void checking(ExpectationBuilder expectations) {
+        // do nothing
+    }
+    
+    /**
+     * Install expectations on mock domain services.
+     * 
+     * <p>
+     * This implementation is a no-op, but subclasses of this class tailored to
+     * supporting unit specs/tests are expected to override.
+     */
+    public void assertIsSatisfied() {
+        // do nothing
+    }
+    
+    /**
+     * Define {@link Sequence} in a (JMock) interaction.
+     * 
+     * <p>
+     * This implementation is a no-op, but subclasses of this class tailored to
+     * supporting unit specs/tests are expected to override.
+     */
+    public Sequence sequence(String name) {
+        // do nothing
+        return null;
+    }
+    
+    /**
+     * Define {@link States} in a (JMock) interaction.
+     * 
+     * <p>
+     * This implementation is a no-op, but subclasses of this class tailored to
+     * supporting unit specs/tests are expected to override.
+     */
+    public States states(String name) {
+        // do nothing
+        return null;
+    }
+    
+    // //////////////////////////////////////
+
+    /**
      * Install arbitrary fixtures, eg before an integration tests or as part of a 
      * Cucumber step definitions or hook.
      * 
      * <p>
-     * This implementation has a no-op, but subclasses of this class tailored to
+     * This implementation is a no-op, but subclasses of this class tailored to
      * supporting integration specs/tests are expected to override.
      */
     public void install(InstallableFixture... fixtures) {
@@ -264,7 +317,7 @@ public class ScenarioExecution {
      * For Cucumber hooks to call, performing transaction management around each step.
      * 
      * <p>
-     * This implementation has a no-op, but subclasses of this class tailored to
+     * This implementation is a no-op, but subclasses of this class tailored to
      * supporting integration specs are expected to override.  (Integration tests can use
      * the <tt>IsisTransactionRule</tt> to do transaction management transparently).
      */
@@ -276,7 +329,7 @@ public class ScenarioExecution {
      * For Cucumber hooks to call, performing transaction management around each step.
      * 
      * <p>
-     * This implementation has a no-op, but subclasses of this class tailored to
+     * This implementation is a no-op, but subclasses of this class tailored to
      * supporting integration specs are expected to override.  (Integration tests can use
      * the <tt>IsisTransactionRule</tt> to do transaction management transparently).
      */
@@ -284,4 +337,30 @@ public class ScenarioExecution {
         // do nothing
     }
 
+    // //////////////////////////////////////
+
+    public Object injectServices(final Object obj) {
+        try {
+            final Method[] methods = obj.getClass().getMethods();
+            for (Method method : methods) {
+                final Class<?>[] parameterTypes = method.getParameterTypes();
+                if(parameterTypes.length != 1) {
+                    continue;
+                }
+                final Class<?> serviceClass = parameterTypes[0];
+                if(method.getName().startsWith("inject")) {
+                    final Object service = service(serviceClass);
+                        method.invoke(obj, service);
+                }
+                if(method.getName().startsWith("set") && serviceClass == DomainObjectContainer.class) {
+                    final Object container = container();
+                    method.invoke(obj, container);
+                }
+            }
+            return obj;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/68136cc7/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/ScenarioExecutionForUnit.java
----------------------------------------------------------------------
diff --git a/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/ScenarioExecutionForUnit.java b/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/ScenarioExecutionForUnit.java
new file mode 100644
index 0000000..c8e0531
--- /dev/null
+++ b/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/ScenarioExecutionForUnit.java
@@ -0,0 +1,131 @@
+/**
+ *  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.core.specsupport.scenarios;
+
+import java.util.Map;
+
+import com.google.common.collect.Maps;
+
+import org.hamcrest.Description;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.Sequence;
+import org.jmock.States;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+import org.jmock.internal.ExpectationBuilder;
+import org.jmock.lib.legacy.ClassImposteriser;
+
+import org.apache.isis.applib.DomainObjectContainer;
+
+/**
+ * An implementation of {@link ScenarioExecution} with which uses JMock to provide
+ * all services.
+ * 
+ * <p>
+ * Expectations can be {@link Mockery#checking(org.jmock.internal.ExpectationBuilder) set} 
+ * and interactions {@link Mockery#assertIsSatisfied() verified} by 
+ * {@link #mockery() accessing} the underlying {@link Mockery}.  
+ */
+public class ScenarioExecutionForUnit extends ScenarioExecution {
+
+    private static class DomainServiceProviderMockery implements DomainServiceProvider {
+
+        private DomainObjectContainer mockContainer = null;
+        private final Map<Class<?>, Object> mocks = Maps.newHashMap();
+        
+        private final Mockery context = new Mockery() {{
+            setImposteriser(ClassImposteriser.INSTANCE);
+        }};
+        private ScenarioExecution scenarioExecution;
+
+        @Override
+        public DomainObjectContainer getContainer() {
+            if(mockContainer == null) {
+                mockContainer = getService(DomainObjectContainer.class);
+                context.checking(new Expectations() {
+                    {
+                        allowing(mockContainer).newTransientInstance(with(Expectations.<Class<?>>anything()));
+                        will(new Action() {
+                            
+                            @SuppressWarnings("rawtypes")
+                            public Object invoke(Invocation invocation) throws Throwable {
+                                Class cls = (Class) invocation.getParameter(0);
+                                return scenarioExecution.injectServices(cls.newInstance());
+                            }
+                            
+                            public void describeTo(Description description) {
+                                description.appendText("newTransientInstance");
+                            }
+                        });
+                        
+                        allowing(mockContainer).persistIfNotAlready(with(anything()));
+                    }
+                });
+            }
+            return mockContainer;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public <T> T getService(Class<T> serviceClass) {
+            Object mock = mocks.get(serviceClass);
+            if(mock == null) {
+                mock = context.mock(serviceClass);
+            }
+            mocks.put(serviceClass, mock);
+            return (T) mock;
+        }
+        
+        public Mockery mockery() {
+            return context;
+        }
+
+        private DomainServiceProviderMockery init(ScenarioExecution scenarioExecution) {
+            this.scenarioExecution = scenarioExecution;
+            return this;
+        }
+    }
+
+    private final ScenarioExecutionForUnit.DomainServiceProviderMockery dspm;
+    
+    public ScenarioExecutionForUnit() {
+        this(new DomainServiceProviderMockery());
+    }
+    private ScenarioExecutionForUnit(ScenarioExecutionForUnit.DomainServiceProviderMockery dspm) {
+        super(dspm);
+        this.dspm = dspm.init(this);
+    }
+    
+    // //////////////////////////////////////
+
+    public void checking(ExpectationBuilder expectations) {
+        dspm.mockery().checking(expectations);
+    }
+    
+    public void assertIsSatisfied() {
+        dspm.mockery().assertIsSatisfied();
+    }
+    
+    public Sequence sequence(String name) {
+        return dspm.mockery().sequence(name);
+    }
+    public States states(String name) {
+        return dspm.mockery().states(name);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/68136cc7/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/ScenarioExecutionScope.java
----------------------------------------------------------------------
diff --git a/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/ScenarioExecutionScope.java b/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/ScenarioExecutionScope.java
index 3c8f487..a6a89db 100644
--- a/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/ScenarioExecutionScope.java
+++ b/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/ScenarioExecutionScope.java
@@ -23,7 +23,8 @@ package org.apache.isis.core.specsupport.scenarios;
  */
 public class ScenarioExecutionScope {
     
-    public final static ScenarioExecutionScope UNIT = new ScenarioExecutionScope(ScenarioExecution.class);
+    public final static ScenarioExecutionScope UNIT = new ScenarioExecutionScope(ScenarioExecutionForUnit.class);
+    public final static ScenarioExecutionScope INTEGRATION = new ScenarioExecutionScope("org.apache.isis.core.integtestsupport.scenarios.ScenarioExecutionForIntegration");
     
     private final Class<? extends ScenarioExecution> scenarioExecutionClass;
 
@@ -31,6 +32,16 @@ public class ScenarioExecutionScope {
         this.scenarioExecutionClass = scenarioExecutionClass;
     }
     
+    @SuppressWarnings("unchecked")
+    public ScenarioExecutionScope(String scenarioExecutionClassName) {
+        try {
+            this.scenarioExecutionClass = (Class<? extends ScenarioExecution>) 
+                    Thread.currentThread().getContextClassLoader().loadClass(scenarioExecutionClassName);
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     public ScenarioExecution instantiate() {
         try {
             return scenarioExecutionClass.newInstance();

http://git-wip-us.apache.org/repos/asf/isis/blob/68136cc7/core/specsupport/src/main/java/org/apache/isis/core/specsupport/specs/CukeStepDefsAbstract.java
----------------------------------------------------------------------
diff --git a/core/specsupport/src/main/java/org/apache/isis/core/specsupport/specs/CukeStepDefsAbstract.java b/core/specsupport/src/main/java/org/apache/isis/core/specsupport/specs/CukeStepDefsAbstract.java
index 190a39e..2e497ac 100644
--- a/core/specsupport/src/main/java/org/apache/isis/core/specsupport/specs/CukeStepDefsAbstract.java
+++ b/core/specsupport/src/main/java/org/apache/isis/core/specsupport/specs/CukeStepDefsAbstract.java
@@ -25,6 +25,9 @@ import java.util.List;
 
 import com.google.common.collect.Lists;
 
+import org.jmock.Sequence;
+import org.jmock.States;
+import org.jmock.internal.ExpectationBuilder;
 import org.junit.Assert;
 
 import cucumber.api.java.Before;
@@ -32,6 +35,7 @@ import cucumber.api.java.Before;
 import org.apache.isis.applib.DomainObjectContainer;
 import org.apache.isis.applib.services.wrapper.WrapperFactory;
 import org.apache.isis.core.specsupport.scenarios.ScenarioExecution;
+import org.apache.isis.core.specsupport.scenarios.ScenarioExecutionForUnit;
 import org.apache.isis.core.specsupport.scenarios.ScenarioExecutionScope;
 
 
@@ -66,6 +70,7 @@ public abstract class CukeStepDefsAbstract {
      */
     public void put(String type, String id, Object value) {
         scenarioExecution().put(type, id, value);
+        
     }
     
     /**
@@ -107,16 +112,44 @@ public abstract class CukeStepDefsAbstract {
      * Convenience method
      */
     protected <T> T wrap(T obj) {
-        return scenarioExecution.wrapperFactory().wrap(obj);
+        return wrapperFactory().wrap(obj);
     }
     
     /**
      * Convenience method
      */
     protected <T> T unwrap(T obj) {
-        return scenarioExecution.wrapperFactory().unwrap(obj);
+        return wrapperFactory().unwrap(obj);
+    }
+    
+    /**
+     * Convenience method
+     */
+    public void checking(ExpectationBuilder expectations) {
+        scenarioExecution().checking(expectations);
     }
     
+    /**
+     * Convenience method
+     */
+    public void assertIsSatisfied() {
+        scenarioExecution().assertIsSatisfied();
+    }
+    
+    /**
+     * Convenience method
+     */
+    public Sequence sequence(String name) {
+        return scenarioExecution().sequence(name);
+    }
+    
+    /**
+     * Convenience method
+     */
+    public States states(String name) {
+        return scenarioExecution().states(name);
+    }
+
     // //////////////////////////////////////
 
     @SuppressWarnings({ "rawtypes", "unchecked" })
@@ -222,9 +255,17 @@ public abstract class CukeStepDefsAbstract {
      *     before(ScenarioExecutionScope.INTEGRATION);
      *  }
      * </pre>
-     * where <tt>ScenarioExecutionForMyAppIntegration</tt> is an application-specific subclass of
-     * {@link ScenarioExecution} for integration-testing.  Typically this is done using the 
-     * <tt>IsisSystemForTest</tt> class provided in the <tt>isis-core-integtestsupport</tt> module).
+     * The built-in {@link ScenarioExecutionScope#UNIT unit}-level scope will instantiate a 
+     * {@link ScenarioExecutionForUnit}, while the built-in
+     * {@link ScenarioExecutionScope#INTEGRATION integration}-level scope instantiates 
+     * <tt>ScenarioExecutionForIntegration</tt> (from the <tt>isis-core-integtestsupport</tt> module).  
+     * The former provides access to domain services as mocks, whereas the latter wraps a running 
+     * <tt>IsisSystemForTest</tt>.
+     * 
+     * <p>
+     * If need be, it is also possible to define custom scopes, with a different implementation of 
+     * {@link ScenarioExecution}.  This might be done when unit testing where a large number of specs
+     * have similar expectations needing to be set on the mock domain services.
      * 
      * <p>
      * Not every class holding step definitions should have these hooks, only those that correspond to the logical