You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2011/02/19 21:37:13 UTC

svn commit: r1072436 - in /cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src: main/java/org/apache/cayenne/ main/java/org/apache/cayenne/access/ test/java/org/apache/cayenne/ test/java/org/apache/cayenne/remote/

Author: aadamchik
Date: Sat Feb 19 20:37:12 2011
New Revision: 1072436

URL: http://svn.apache.org/viewvc?rev=1072436&view=rev
Log:
CAY-1536 Expand deserialization context to provide Injector object instead of just a DataChannel

restoring lazy deserialization mechanism for context, only now it is specified in the superclass - in progress

Removed:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/GeneratedMethodsInClientObjectContextTest.java
Modified:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/BaseContext.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContext.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContext.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/BaseContextTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/CayenneContextTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/CayenneContextWithDataContextTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/ClientChannelTest.java

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/BaseContext.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/BaseContext.java?rev=1072436&r1=1072435&r2=1072436&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/BaseContext.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/BaseContext.java Sat Feb 19 20:37:12 2011
@@ -96,6 +96,7 @@ public abstract class BaseContext implem
     // registry
     protected transient DataChannel channel;
     protected transient QueryCache queryCache;
+    protected transient EntityResolver entityResolver;
 
     /**
      * Graph action that handles property changes
@@ -129,7 +130,7 @@ public abstract class BaseContext implem
      *         it was already attached.
      * @since 3.1
      */
-    protected boolean attachIfNeeded() {
+    protected boolean attachToRuntimeIfNeeded() {
         if (channel != null) {
             return false;
         }
@@ -140,12 +141,37 @@ public abstract class BaseContext implem
                     + "Null injector returned from CayenneRuntime.getThreadInjector()");
         }
 
-        setChannel(injector.getInstance(DataChannel.class));
+        attachToRuntime(injector);
+        return true;
+    }
+
+    /**
+     * Attaches this context to the Cayenne runtime whose Injector is passed as an
+     * argument to this method.
+     * 
+     * @since 3.1
+     */
+    protected void attachToRuntime(Injector injector) {
+        attachToChannel(injector.getInstance(DataChannel.class));
+
         setQueryCache(injector.getInstance(Key.get(
                 QueryCache.class,
                 QUERY_CACHE_INJECTION_KEY)));
+    }
 
-        return true;
+    /**
+     * Initializes a DataChannel for this context, setting internal state using
+     * information from the
+     * 
+     * @since 3.1
+     */
+    protected void attachToChannel(DataChannel channel) {
+        if (channel == null) {
+            throw new NullPointerException("Null channel");
+        }
+
+        setChannel(channel);
+        setEntityResolver(channel.getEntityResolver());
     }
 
     public abstract void commitChanges();
@@ -155,17 +181,30 @@ public abstract class BaseContext implem
     public abstract Collection<?> deletedObjects();
 
     public DataChannel getChannel() {
+        attachToRuntimeIfNeeded();
         return channel;
     }
-    
+
     /**
+     * Sets a new DataChannel for this context.
+     * 
      * @since 3.1
      */
     public void setChannel(DataChannel channel) {
         this.channel = channel;
     }
 
-    public abstract EntityResolver getEntityResolver();
+    public EntityResolver getEntityResolver() {
+        attachToRuntimeIfNeeded();
+        return entityResolver;
+    }
+
+    /**
+     * @since 3.1
+     */
+    public void setEntityResolver(EntityResolver entityResolver) {
+        this.entityResolver = entityResolver;
+    }
 
     public abstract GraphManager getGraphManager();
 
@@ -303,6 +342,7 @@ public abstract class BaseContext implem
     public abstract Collection<?> uncommittedObjects();
 
     public QueryCache getQueryCache() {
+        attachToRuntimeIfNeeded();
         return queryCache;
     }
 

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContext.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContext.java?rev=1072436&r1=1072435&r2=1072436&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContext.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/CayenneContext.java Sat Feb 19 20:37:12 2011
@@ -29,7 +29,6 @@ import org.apache.cayenne.event.EventMan
 import org.apache.cayenne.graph.CompoundDiff;
 import org.apache.cayenne.graph.GraphDiff;
 import org.apache.cayenne.graph.GraphManager;
-import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.reflect.ClassDescriptor;
@@ -46,8 +45,6 @@ import org.apache.cayenne.validation.Val
  */
 public class CayenneContext extends BaseContext {
 
-    protected EntityResolver entityResolver;
-
     CayenneContextGraphManager graphManager;
 
     // object that merges "backdoor" changes that come from the channel.
@@ -75,39 +72,36 @@ public class CayenneContext extends Base
     public CayenneContext(DataChannel channel, boolean changeEventsEnabled,
             boolean lifecyleEventsEnabled) {
 
-        this.graphManager = new CayenneContextGraphManager(
+        graphManager = new CayenneContextGraphManager(
                 this,
                 changeEventsEnabled,
                 lifecyleEventsEnabled);
 
-        setChannel(channel);
+        if (channel != null) {
+            attachToChannel(channel);
+        }
     }
 
     /**
-     * Sets the context channel, setting up a listener for channel events.
+     * @since 3.1
      */
     @Override
-    public void setChannel(DataChannel channel) {
-        if (this.channel != channel) {
+    protected void attachToChannel(DataChannel channel) {
+        super.attachToChannel(channel);
 
-            if (this.mergeHandler != null) {
-                this.mergeHandler.active = false;
-                this.mergeHandler = null;
-            }
+        if (mergeHandler != null) {
+            mergeHandler.active = false;
+            mergeHandler = null;
+        }
 
-            this.channel = channel;
+        EventManager eventManager = channel.getEventManager();
+        if (eventManager != null) {
+            mergeHandler = new CayenneContextMergeHandler(this);
 
-            EventManager eventManager = (channel != null)
-                    ? channel.getEventManager()
-                    : null;
-            if (eventManager != null) {
-                this.mergeHandler = new CayenneContextMergeHandler(this);
-
-                // listen to our channel events...
-                // note that we must reset listener on channel switch, as there is no
-                // guarantee that a new channel uses the same EventManager.
-                EventUtil.listenForChannelEvents(channel, mergeHandler);
-            }
+            // listen to our channel events...
+            // note that we must reset listener on channel switch, as there is no
+            // guarantee that a new channel uses the same EventManager.
+            EventUtil.listenForChannelEvents(channel, mergeHandler);
         }
     }
 
@@ -130,29 +124,6 @@ public class CayenneContext extends Base
         return graphManager.lifecycleEventsEnabled;
     }
 
-    /**
-     * Returns an EntityResolver that provides mapping information needed for
-     * CayenneContext operation. If EntityResolver is not set, this method would obtain
-     * and cache one from the underlying DataChannel.
-     */
-    @Override
-    public EntityResolver getEntityResolver() {
-        // load entity resolver on demand
-        if (entityResolver == null) {
-            synchronized (this) {
-                if (entityResolver == null) {
-                    setEntityResolver(channel.getEntityResolver());
-                }
-            }
-        }
-
-        return entityResolver;
-    }
-
-    public void setEntityResolver(EntityResolver entityResolver) {
-        this.entityResolver = entityResolver;
-    }
-
     @Override
     public GraphManager getGraphManager() {
         return graphManager;
@@ -488,7 +459,7 @@ public class CayenneContext extends Base
 
         // TODO: This method should be deprecated and child context should be created via
         // DI with all proper injection, so won't have to guess how to handle query cache.
- if (queryCache != null) {
+        if (queryCache != null) {
             child.setQueryCache(new NestedQueryCache(queryCache));
         }
 

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContext.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContext.java?rev=1072436&r1=1072435&r2=1072436&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContext.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataContext.java Sat Feb 19 20:37:12 2011
@@ -50,7 +50,6 @@ import org.apache.cayenne.graph.GraphDif
 import org.apache.cayenne.graph.GraphManager;
 import org.apache.cayenne.map.DbJoin;
 import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.map.ObjAttribute;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.ObjRelationship;
@@ -79,10 +78,6 @@ public class DataContext extends BaseCon
     protected boolean validatingObjectsOnCommit;
     protected ObjectStore objectStore;
 
-    // note that entity resolver is initialized from the parent channel the first time it
-    // is accessed, and later cached in the context
-    protected transient EntityResolver entityResolver;
-
     protected transient DataContextMergeHandler mergeHandler;
 
     /**
@@ -98,8 +93,10 @@ public class DataContext extends BaseCon
      * @since 1.2
      */
     public DataContext(DataChannel channel, ObjectStore objectStore) {
-        // use a setter to properly initialize EntityResolver
-        setChannel(channel);
+
+        if (channel != null) {
+            attachToChannel(channel);
+        }
 
         // inject self as parent context
         if (objectStore != null) {
@@ -137,45 +134,34 @@ public class DataContext extends BaseCon
     }
 
     /**
-     * @since 1.2
+     * @since 3.1
      */
     @Override
-    public void setChannel(DataChannel channel) {
-        if (this.channel != channel) {
+    protected void attachToChannel(DataChannel channel) {
 
-            if (this.mergeHandler != null) {
-                this.mergeHandler.setActive(false);
-            }
+        super.attachToChannel(channel);
 
-            this.entityResolver = null;
-            this.mergeHandler = null;
+        if (mergeHandler != null) {
+            mergeHandler.setActive(false);
+            mergeHandler = null;
+        }
 
-            this.channel = channel;
+        EventManager eventManager = channel.getEventManager();
 
-            if (channel != null) {
+        if (eventManager != null) {
+            mergeHandler = new DataContextMergeHandler(this);
 
-                // cache entity resolver, as we have no idea how expensive it is to query
-                // it on the channel every time
-                this.entityResolver = channel.getEntityResolver();
-
-                EventManager eventManager = channel.getEventManager();
-
-                if (eventManager != null) {
-                    this.mergeHandler = new DataContextMergeHandler(this);
-
-                    // listen to our channel events...
-                    // note that we must reset listener on channel switch, as there is no
-                    // guarantee that a new channel uses the same EventManager.
-                    EventUtil.listenForChannelEvents(channel, mergeHandler);
-                }
+            // listen to our channel events...
+            // note that we must reset listener on channel switch, as there is no
+            // guarantee that a new channel uses the same EventManager.
+            EventUtil.listenForChannelEvents(channel, mergeHandler);
+        }
 
-                if (!usingSharedSnaphsotCache && getObjectStore() != null) {
-                    DataRowStore cache = getObjectStore().getDataRowCache();
+        if (!usingSharedSnaphsotCache && getObjectStore() != null) {
+            DataRowStore cache = getObjectStore().getDataRowCache();
 
-                    if (cache != null) {
-                        cache.setEventManager(eventManager);
-                    }
-                }
+            if (cache != null) {
+                cache.setEventManager(eventManager);
             }
         }
     }
@@ -190,6 +176,7 @@ public class DataContext extends BaseCon
      * @since 1.1
      */
     public DataDomain getParentDataDomain() {
+        attachToRuntimeIfNeeded();
 
         if (channel == null) {
             return null;
@@ -1066,15 +1053,6 @@ public class DataContext extends BaseCon
     }
 
     /**
-     * Returns EntityResolver. EntityResolver can be null if DataContext has not been
-     * attached to an DataChannel.
-     */
-    @Override
-    public EntityResolver getEntityResolver() {
-        return entityResolver;
-    }
-
-    /**
      * Returns <code>true</code> if the ObjectStore uses shared cache of a parent
      * DataDomain.
      * 

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/BaseContextTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/BaseContextTest.java?rev=1072436&r1=1072435&r2=1072436&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/BaseContextTest.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/BaseContextTest.java Sat Feb 19 20:37:12 2011
@@ -43,7 +43,7 @@ public class BaseContextTest extends Tes
         assertSame(properties, context.getUserProperties());
     }
 
-    public void testAttachIfNeeded() {
+    public void testAttachToRuntimeIfNeeded() {
 
         final DataChannel channel = mock(DataChannel.class);
         final QueryCache cache = mock(QueryCache.class);
@@ -70,26 +70,26 @@ public class BaseContextTest extends Tes
 
             CayenneRuntime.bindThreadInjector(injector);
 
-            assertTrue(context.attachIfNeeded());
+            assertTrue(context.attachToRuntimeIfNeeded());
             assertSame(channel, context.channel);
             assertSame(cache, context.queryCache);
 
-            assertFalse(context.attachIfNeeded());
-            assertFalse(context.attachIfNeeded());
+            assertFalse(context.attachToRuntimeIfNeeded());
+            assertFalse(context.attachToRuntimeIfNeeded());
         }
         finally {
             CayenneRuntime.bindThreadInjector(oldInjector);
         }
     }
 
-    public void testAttachIfNeeded_NoStack() {
+    public void testAttachToRuntimeIfNeeded_NoStack() {
 
         BaseContext context = new MockBaseContext();
         assertNull(context.channel);
         assertNull(context.queryCache);
 
         try {
-            context.attachIfNeeded();
+            context.attachToRuntimeIfNeeded();
             fail("No thread stack, must have thrown");
         }
         catch (CayenneRuntimeException e) {

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/CayenneContextTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/CayenneContextTest.java?rev=1072436&r1=1072435&r2=1072436&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/CayenneContextTest.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/CayenneContextTest.java Sat Feb 19 20:37:12 2011
@@ -19,6 +19,10 @@
 
 package org.apache.cayenne;
 
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -37,13 +41,17 @@ import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.query.Query;
+import org.apache.cayenne.remote.BootstrapMessage;
 import org.apache.cayenne.remote.ClientChannel;
-import org.apache.cayenne.remote.MockClientConnection;
+import org.apache.cayenne.remote.ClientConnection;
+import org.apache.cayenne.remote.ClientMessage;
 import org.apache.cayenne.testdo.mt.ClientMtTable1;
 import org.apache.cayenne.testdo.mt.MtTable1;
 import org.apache.cayenne.unit.di.client.ClientCase;
 import org.apache.cayenne.unit.di.server.UseServerRuntime;
 import org.apache.cayenne.util.GenericResponse;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 @UseServerRuntime(ClientCase.MULTI_TIER_PROJECT)
 public class CayenneContextTest extends ClientCase {
@@ -57,7 +65,7 @@ public class CayenneContextTest extends 
 
         // test default property parameters
         assertNotNull(context.getGraphManager());
-        assertNull(context.getChannel());
+        assertNull(context.channel);
 
         MockDataChannel channel = new MockDataChannel();
         context.setChannel(channel);
@@ -66,7 +74,8 @@ public class CayenneContextTest extends 
 
     public void testLocalObject() {
 
-        MockDataChannel channel = new MockDataChannel();
+        DataChannel channel = mock(DataChannel.class);
+
         CayenneContext src = new CayenneContext(channel);
         src
                 .setEntityResolver(serverContext
@@ -291,8 +300,22 @@ public class CayenneContextTest extends 
         inflated.setObjectId(gid);
         inflated.setGlobalAttribute1("abc");
 
-        MockClientConnection connection = new MockClientConnection(new GenericResponse(
-                Arrays.asList(inflated)));
+        ClientConnection connection = mock(ClientConnection.class);
+        when(connection.sendMessage((ClientMessage) any())).thenAnswer(
+                new Answer<Object>() {
+
+                    public Object answer(InvocationOnMock invocation) {
+                        ClientMessage arg = (ClientMessage) invocation.getArguments()[0];
+
+                        if (arg instanceof BootstrapMessage) {
+                            return new EntityResolver();
+                        }
+                        else {
+                            return new GenericResponse(Arrays.asList(inflated));
+                        }
+                    }
+                });
+
         ClientChannel channel = new ClientChannel(
                 connection,
                 false,
@@ -341,8 +364,21 @@ public class CayenneContextTest extends 
         inflated.setObjectId(gid);
         inflated.setGlobalAttribute1("abc");
 
-        MockClientConnection connection = new MockClientConnection(new GenericResponse(
-                Arrays.asList(inflated)));
+        ClientConnection connection = mock(ClientConnection.class);
+        when(connection.sendMessage((ClientMessage) any())).thenAnswer(
+                new Answer<Object>() {
+
+                    public Object answer(InvocationOnMock invocation) {
+                        ClientMessage arg = (ClientMessage) invocation.getArguments()[0];
+
+                        if (arg instanceof BootstrapMessage) {
+                            return new EntityResolver();
+                        }
+                        else {
+                            return new GenericResponse(Arrays.asList(inflated));
+                        }
+                    }
+                });
         ClientChannel channel = new ClientChannel(
                 connection,
                 false,

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/CayenneContextWithDataContextTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/CayenneContextWithDataContextTest.java?rev=1072436&r1=1072435&r2=1072436&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/CayenneContextWithDataContextTest.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/CayenneContextWithDataContextTest.java Sat Feb 19 20:37:12 2011
@@ -119,6 +119,34 @@ public class CayenneContextWithDataConte
         assertEquals(0, serverContext.getQueryCache().size());
     }
 
+    public void testAddToList() throws Exception {
+
+        ClientMtTable1 t1 = clientContext.newObject(ClientMtTable1.class);
+        ClientMtTable2 t2 = clientContext.newObject(ClientMtTable2.class);
+
+        t1.addToTable2Array(t2);
+        assertEquals(1, t1.getTable2Array().size());
+        assertSame(t1, t2.getTable1());
+
+        // do it again to make sure action can handle series of changes
+        ClientMtTable1 t3 = clientContext.newObject(ClientMtTable1.class);
+        ClientMtTable2 t4 = clientContext.newObject(ClientMtTable2.class);
+
+        t3.addToTable2Array(t4);
+        assertEquals(1, t3.getTable2Array().size());
+        assertSame(t3, t4.getTable1());
+    }
+
+    public void testSetValueHolder() throws Exception {
+
+        ClientMtTable1 t1 = clientContext.newObject(ClientMtTable1.class);
+        ClientMtTable2 t2 = clientContext.newObject(ClientMtTable2.class);
+
+        t2.setTable1(t1);
+        assertEquals(1, t1.getTable2Array().size());
+        assertSame(t1, t2.getTable1());
+    }
+
     public void testPostAddCallback() throws Exception {
 
         LifecycleCallbackRegistry callbackRegistry = clientServerChannel

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/ClientChannelTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/ClientChannelTest.java?rev=1072436&r1=1072435&r2=1072436&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/ClientChannelTest.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/remote/ClientChannelTest.java Sat Feb 19 20:37:12 2011
@@ -19,6 +19,10 @@
 
 package org.apache.cayenne.remote;
 
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -41,16 +45,32 @@ import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.unit.CayenneCase;
 import org.apache.cayenne.util.GenericResponse;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 public class ClientChannelTest extends CayenneCase {
 
     public void testOnQuerySelect() {
+
         final MockPersistentObject o1 = new MockPersistentObject();
         ObjectId oid1 = new ObjectId("test_entity");
         o1.setObjectId(oid1);
 
-        MockClientConnection connection = new MockClientConnection(new GenericResponse(
-                Arrays.asList(o1)));
+        ClientConnection connection = mock(ClientConnection.class);
+        when(connection.sendMessage((ClientMessage) any())).thenAnswer(
+                new Answer<Object>() {
+
+                    public Object answer(InvocationOnMock invocation) {
+                        ClientMessage arg = (ClientMessage) invocation.getArguments()[0];
+
+                        if (arg instanceof BootstrapMessage) {
+                            return new EntityResolver();
+                        }
+                        else {
+                            return new GenericResponse(Arrays.asList(o1));
+                        }
+                    }
+                });
 
         ClientChannel channel = new ClientChannel(
                 connection,
@@ -146,7 +166,8 @@ public class ClientChannelTest extends C
         MockClientConnection connection = new MockClientConnection(new GenericResponse(
                 Arrays.asList(o2)));
 
-        ClientChannel channel = new ClientChannel(connection,
+        ClientChannel channel = new ClientChannel(
+                connection,
                 false,
                 new MockEventManager(),
                 false);
@@ -189,10 +210,7 @@ public class ClientChannelTest extends C
 
         // default constructor must fail
         try {
-            new ClientChannel(connection,
-                    false,
-                    new MockEventManager(),
-                    false);
+            new ClientChannel(connection, false, new MockEventManager(), false);
             fail("Channel didn't throw on broken EventBridge");
         }
         catch (CayenneRuntimeException e) {