You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by cr...@apache.org on 2021/10/01 01:05:53 UTC

[dubbo] branch 3.0 updated: [3.0] Enhance mergeable cluster invoker test (#8889)

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

crazyhzm pushed a commit to branch 3.0
in repository https://gitbox.apache.org/repos/asf/dubbo.git


The following commit(s) were added to refs/heads/3.0 by this push:
     new 933d2cd  [3.0] Enhance mergeable cluster invoker test (#8889)
933d2cd is described below

commit 933d2cd5b97923bdac77bf7bf32063ca3897835c
Author: Wang Chengming <63...@qq.com>
AuthorDate: Fri Oct 1 09:05:32 2021 +0800

    [3.0] Enhance mergeable cluster invoker test (#8889)
    
    * 1.add some MergeableClusterInvoker unit test
    2.pretty code
    
    * add MergeableClusterInvoker unit test
    
    * add MergeableClusterInvoker unit test
---
 .../cluster/support/MergeableClusterInvoker.java   |  20 +-
 .../support/MergeableClusterInvokerTest.java       | 378 +++++++++++++++++++--
 2 files changed, 355 insertions(+), 43 deletions(-)

diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/MergeableClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/MergeableClusterInvoker.java
index 315596c..1253b1a 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/MergeableClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/MergeableClusterInvoker.java
@@ -67,7 +67,8 @@ public class MergeableClusterInvoker<T> extends AbstractClusterInvoker<T> {
                         return invokeWithContext(invoker, invocation);
                     } catch (RpcException e) {
                         if (e.isNoInvokerAvailableAfterFilter()) {
-                            log.debug("No available provider for service" + getUrl().getServiceKey() + " on group " + invoker.getUrl().getGroup() + ", will continue to try another group.");
+                            log.debug("No available provider for service" + getUrl().getServiceKey() + " on group "
+                                + invoker.getUrl().getGroup() + ", will continue to try another group.");
                         } else {
                             throw e;
                         }
@@ -79,8 +80,7 @@ public class MergeableClusterInvoker<T> extends AbstractClusterInvoker<T> {
 
         Class<?> returnType;
         try {
-            returnType = getInterface().getMethod(
-                    invocation.getMethodName(), invocation.getParameterTypes()).getReturnType();
+            returnType = getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes()).getReturnType();
         } catch (NoSuchMethodException e) {
             returnType = null;
         }
@@ -92,9 +92,9 @@ public class MergeableClusterInvoker<T> extends AbstractClusterInvoker<T> {
             results.put(invoker.getUrl().getServiceKey(), invokeWithContext(invoker, subInvocation));
         }
 
-        Object result = null;
+        Object result;
 
-        List<Result> resultList = new ArrayList<Result>(results.size());
+        List<Result> resultList = new ArrayList<>(results.size());
 
         for (Map.Entry<String, Result> entry : results.entrySet()) {
             Result asyncResult = entry.getValue();
@@ -102,8 +102,7 @@ public class MergeableClusterInvoker<T> extends AbstractClusterInvoker<T> {
                 Result r = asyncResult.get(Integer.MAX_VALUE, TimeUnit.MILLISECONDS);
                 if (r.hasException()) {
                     log.error("Invoke " + getGroupDescFromServiceKey(entry.getKey()) +
-                                    " failed: " + r.getException().getMessage(),
-                            r.getException());
+                        " failed: " + r.getException().getMessage(), r.getException());
                 } else {
                     resultList.add(r);
                 }
@@ -137,7 +136,7 @@ public class MergeableClusterInvoker<T> extends AbstractClusterInvoker<T> {
             result = resultList.remove(0).getValue();
             try {
                 if (method.getReturnType() != void.class
-                        && method.getReturnType().isAssignableFrom(result.getClass())) {
+                    && method.getReturnType().isAssignableFrom(result.getClass())) {
                     for (Result r : resultList) {
                         result = method.invoke(result, r.getValue());
                     }
@@ -159,12 +158,11 @@ public class MergeableClusterInvoker<T> extends AbstractClusterInvoker<T> {
                 resultMerger = applicationModel.getExtensionLoader(Merger.class).getExtension(merger);
             }
             if (resultMerger != null) {
-                List<Object> rets = new ArrayList<Object>(resultList.size());
+                List<Object> rets = new ArrayList<>(resultList.size());
                 for (Result r : resultList) {
                     rets.add(r.getValue());
                 }
-                result = resultMerger.merge(
-                        rets.toArray((Object[]) Array.newInstance(returnType, 0)));
+                result = resultMerger.merge(rets.toArray((Object[]) Array.newInstance(returnType, 0)));
             } else {
                 throw new RpcException("There is no merger to merge result.");
             }
diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/MergeableClusterInvokerTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/MergeableClusterInvokerTest.java
index 975eee6..bb2ff1b 100644
--- a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/MergeableClusterInvokerTest.java
+++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/MergeableClusterInvokerTest.java
@@ -22,7 +22,10 @@ import org.apache.dubbo.rpc.AsyncRpcResult;
 import org.apache.dubbo.rpc.Invocation;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Result;
+import org.apache.dubbo.rpc.RpcException;
 import org.apache.dubbo.rpc.cluster.Directory;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ModuleModel;
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -38,7 +41,12 @@ import java.util.Map;
 
 import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
 import static org.apache.dubbo.rpc.Constants.MERGER_KEY;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
 import static org.mockito.BDDMockito.given;
 import static org.mockito.Mockito.mock;
 
@@ -48,30 +56,28 @@ public class MergeableClusterInvokerTest {
     private Invoker firstInvoker = mock(Invoker.class);
     private Invoker secondInvoker = mock(Invoker.class);
     private Invocation invocation = mock(Invocation.class);
+    private ModuleModel moduleModel = mock(ModuleModel.class);
 
     private MergeableClusterInvoker<MenuService> mergeableClusterInvoker;
 
     private String[] list1 = {"10", "11", "12"};
     private String[] list2 = {"20", "21", "22"};
-    private String[] list3 = {"23", "24", "25"};
-    private String[] list4 = {"30", "31", "32"};
-
-    private Map<String, List<String>> firstMenuMap = new HashMap<String, List<String>>() {
+    private final Map<String, List<String>> firstMenuMap = new HashMap<String, List<String>>() {
         {
             put("1", Arrays.asList(list1));
             put("2", Arrays.asList(list2));
         }
     };
-
-    private Map<String, List<String>> secondMenuMap = new HashMap<String, List<String>>() {
+    private final Menu firstMenu = new Menu(firstMenuMap);
+    private String[] list3 = {"23", "24", "25"};
+    private String[] list4 = {"30", "31", "32"};
+    private final Map<String, List<String>> secondMenuMap = new HashMap<String, List<String>>() {
         {
             put("2", Arrays.asList(list3));
             put("3", Arrays.asList(list4));
         }
     };
-
-    private Menu firstMenu = new Menu(firstMenuMap);
-    private Menu secondMenu = new Menu(secondMenuMap);
+    private final Menu secondMenu = new Menu(secondMenuMap);
 
     private URL url = URL.valueOf("test://test/" + MenuService.class.getName());
 
@@ -81,7 +87,7 @@ public class MergeableClusterInvokerTest {
             if (value != null) {
                 value.addAll(entry.getValue());
             } else {
-                first.put(entry.getKey(), new ArrayList<String>(entry.getValue()));
+                first.put(entry.getKey(), new ArrayList<>(entry.getValue()));
             }
         }
     }
@@ -97,7 +103,7 @@ public class MergeableClusterInvokerTest {
     }
 
     @Test
-    public void testGetMenuSuccessfully() throws Exception {
+    public void testGetMenuSuccessfully() {
 
         // setup
         url = url.addParameter(MERGER_KEY, ".merge");
@@ -105,8 +111,7 @@ public class MergeableClusterInvokerTest {
         given(invocation.getMethodName()).willReturn("getMenu");
         given(invocation.getParameterTypes()).willReturn(new Class<?>[]{});
         given(invocation.getArguments()).willReturn(new Object[]{});
-        given(invocation.getObjectAttachments()).willReturn(new HashMap<>())
-                ;
+        given(invocation.getObjectAttachments()).willReturn(new HashMap<>());
         given(invocation.getInvoker()).willReturn(firstInvoker);
 
         firstInvoker = (Invoker) Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[]{Invoker.class}, (proxy, method, args) -> {
@@ -151,17 +156,17 @@ public class MergeableClusterInvokerTest {
 
         // invoke
         Result result = mergeableClusterInvoker.invoke(invocation);
-        Assertions.assertTrue(result.getValue() instanceof Menu);
+        assertTrue(result.getValue() instanceof Menu);
         Menu menu = (Menu) result.getValue();
-        Map<String, List<String>> expected = new HashMap<String, List<String>>();
+        Map<String, List<String>> expected = new HashMap<>();
         merge(expected, firstMenuMap);
         merge(expected, secondMenuMap);
         assertEquals(expected.keySet(), menu.getMenus().keySet());
         for (Map.Entry<String, List<String>> entry : expected.entrySet()) {
             // FIXME: cannot guarantee the sequence of the merge result, check implementation in
             // MergeableClusterInvoker#invoke
-            List<String> values1 = new ArrayList<String>(entry.getValue());
-            List<String> values2 = new ArrayList<String>(menu.getMenus().get(entry.getKey()));
+            List<String> values1 = new ArrayList<>(entry.getValue());
+            List<String> values2 = new ArrayList<>(menu.getMenus().get(entry.getKey()));
             Collections.sort(values1);
             Collections.sort(values2);
             assertEquals(values1, values2);
@@ -169,7 +174,7 @@ public class MergeableClusterInvokerTest {
     }
 
     @Test
-    public void testAddMenu() throws Exception {
+    public void testAddMenu() {
 
         String menu = "first";
         List<String> menuItems = new ArrayList<String>() {
@@ -180,26 +185,19 @@ public class MergeableClusterInvokerTest {
         };
 
         given(invocation.getMethodName()).willReturn("addMenu");
-        given(invocation.getParameterTypes()).willReturn(
-                new Class<?>[]{String.class, List.class});
-        given(invocation.getArguments()).willReturn(new Object[]{menu, menuItems})
-                ;
-        given(invocation.getObjectAttachments()).willReturn(new HashMap<>())
-                ;
+        given(invocation.getParameterTypes()).willReturn(new Class<?>[]{String.class, List.class});
+        given(invocation.getArguments()).willReturn(new Object[]{menu, menuItems});
+        given(invocation.getObjectAttachments()).willReturn(new HashMap<>());
         given(invocation.getInvoker()).willReturn(firstInvoker);
 
-        given(firstInvoker.getUrl()).willReturn(
-                url.addParameter(GROUP_KEY, "first"));
+        given(firstInvoker.getUrl()).willReturn(url.addParameter(GROUP_KEY, "first"));
         given(firstInvoker.getInterface()).willReturn(MenuService.class);
-        given(firstInvoker.invoke(invocation)).willReturn(new AppResponse())
-                ;
+        given(firstInvoker.invoke(invocation)).willReturn(new AppResponse());
         given(firstInvoker.isAvailable()).willReturn(true);
 
-        given(secondInvoker.getUrl()).willReturn(
-                url.addParameter(GROUP_KEY, "second"));
+        given(secondInvoker.getUrl()).willReturn(url.addParameter(GROUP_KEY, "second"));
         given(secondInvoker.getInterface()).willReturn(MenuService.class);
-        given(secondInvoker.invoke(invocation)).willReturn(new AppResponse())
-                ;
+        given(secondInvoker.invoke(invocation)).willReturn(new AppResponse());
         given(secondInvoker.isAvailable()).willReturn(true);
 
         given(directory.list(invocation)).willReturn(new ArrayList() {
@@ -221,4 +219,320 @@ public class MergeableClusterInvokerTest {
 
     }
 
+    @Test
+    public void testAddMenu1() {
+
+        // setup
+        url = url.addParameter(MERGER_KEY, ".merge");
+
+        String menu = "first";
+        List<String> menuItems = new ArrayList<String>() {
+            {
+                add("1");
+                add("2");
+            }
+        };
+
+        given(invocation.getMethodName()).willReturn("addMenu");
+        given(invocation.getParameterTypes()).willReturn(new Class<?>[]{String.class, List.class});
+        given(invocation.getArguments()).willReturn(new Object[]{menu, menuItems});
+        given(invocation.getObjectAttachments()).willReturn(new HashMap<>());
+        given(invocation.getInvoker()).willReturn(firstInvoker);
+
+        firstInvoker = (Invoker) Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[]{Invoker.class}, (proxy, method, args) -> {
+            if ("getUrl".equals(method.getName())) {
+                return url.addParameter(GROUP_KEY, "first");
+            }
+            if ("getInterface".equals(method.getName())) {
+                return MenuService.class;
+            }
+            if ("invoke".equals(method.getName())) {
+                return AsyncRpcResult.newDefaultAsyncResult(firstMenu, invocation);
+            }
+            return null;
+        });
+
+        secondInvoker = (Invoker) Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[]{Invoker.class}, (proxy, method, args) -> {
+            if ("getUrl".equals(method.getName())) {
+                return url.addParameter(GROUP_KEY, "second");
+            }
+            if ("getInterface".equals(method.getName())) {
+                return MenuService.class;
+            }
+            if ("invoke".equals(method.getName())) {
+                return AsyncRpcResult.newDefaultAsyncResult(secondMenu, invocation);
+            }
+            return null;
+        });
+
+        given(directory.list(invocation)).willReturn(new ArrayList() {
+
+            {
+                add(firstInvoker);
+                add(secondInvoker);
+            }
+        });
+        given(directory.getUrl()).willReturn(url);
+        given(directory.getConsumerUrl()).willReturn(url);
+        given(directory.getConsumerUrl()).willReturn(url);
+        given(directory.getInterface()).willReturn(MenuService.class);
+
+        mergeableClusterInvoker = new MergeableClusterInvoker<MenuService>(directory);
+
+        Result result = mergeableClusterInvoker.invoke(invocation);
+        Assertions.assertNull(result.getValue());
+
+    }
+
+    @Test
+    public void testInvokerToNoInvokerAvailableException() {
+        String menu = "first";
+        List<String> menuItems = new ArrayList<String>() {
+            {
+                add("1");
+                add("2");
+            }
+        };
+
+        given(invocation.getMethodName()).willReturn("addMenu");
+        given(invocation.getParameterTypes()).willReturn(new Class<?>[]{String.class, List.class});
+        given(invocation.getArguments()).willReturn(new Object[]{menu, menuItems});
+        given(invocation.getObjectAttachments()).willReturn(new HashMap<>());
+        given(invocation.getInvoker()).willReturn(firstInvoker);
+
+        given(firstInvoker.getUrl()).willReturn(url.addParameter(GROUP_KEY, "first"));
+        given(firstInvoker.getInterface()).willReturn(MenuService.class);
+        given(firstInvoker.invoke(invocation)).willReturn(new AppResponse());
+        given(firstInvoker.isAvailable()).willReturn(true);
+        given(firstInvoker.invoke(invocation)).willThrow(new RpcException(RpcException.NO_INVOKER_AVAILABLE_AFTER_FILTER));
+
+        given(secondInvoker.getUrl()).willReturn(url.addParameter(GROUP_KEY, "second"));
+        given(secondInvoker.getInterface()).willReturn(MenuService.class);
+        given(secondInvoker.invoke(invocation)).willReturn(new AppResponse());
+        given(secondInvoker.isAvailable()).willReturn(true);
+        given(secondInvoker.invoke(invocation)).willThrow(new RpcException(RpcException.NO_INVOKER_AVAILABLE_AFTER_FILTER));
+
+        given(directory.list(invocation)).willReturn(new ArrayList() {
+
+            {
+                add(firstInvoker);
+                add(secondInvoker);
+            }
+        });
+        given(directory.getUrl()).willReturn(url);
+        given(directory.getConsumerUrl()).willReturn(url);
+        given(directory.getConsumerUrl()).willReturn(url);
+        given(directory.getInterface()).willReturn(MenuService.class);
+
+        mergeableClusterInvoker = new MergeableClusterInvoker<MenuService>(directory);
+
+        // invoke
+        try {
+            Result result = mergeableClusterInvoker.invoke(invocation);
+            fail();
+            Assertions.assertNull(result.getValue());
+        } catch (RpcException expected) {
+            assertEquals(expected.getCode(), RpcException.NO_INVOKER_AVAILABLE_AFTER_FILTER);
+        }
+    }
+
+    /**
+     * test when network exception
+     */
+    @Test
+    public void testInvokerToException() {
+        String menu = "first";
+        List<String> menuItems = new ArrayList<String>() {
+            {
+                add("1");
+                add("2");
+            }
+        };
+
+        given(invocation.getMethodName()).willReturn("addMenu");
+        given(invocation.getParameterTypes()).willReturn(new Class<?>[]{String.class, List.class});
+        given(invocation.getArguments()).willReturn(new Object[]{menu, menuItems});
+        given(invocation.getObjectAttachments()).willReturn(new HashMap<>());
+        given(invocation.getInvoker()).willReturn(firstInvoker);
+
+        given(firstInvoker.getUrl()).willReturn(url.addParameter(GROUP_KEY, "first"));
+        given(firstInvoker.getInterface()).willReturn(MenuService.class);
+        given(firstInvoker.invoke(invocation)).willReturn(new AppResponse());
+        given(firstInvoker.isAvailable()).willReturn(true);
+        given(firstInvoker.invoke(invocation)).willThrow(new RpcException(RpcException.NETWORK_EXCEPTION));
+
+        given(secondInvoker.getUrl()).willReturn(url.addParameter(GROUP_KEY, "second"));
+        given(secondInvoker.getInterface()).willReturn(MenuService.class);
+        given(secondInvoker.invoke(invocation)).willReturn(new AppResponse());
+        given(secondInvoker.isAvailable()).willReturn(true);
+        given(secondInvoker.invoke(invocation)).willThrow(new RpcException(RpcException.NETWORK_EXCEPTION));
+
+        given(directory.list(invocation)).willReturn(new ArrayList() {
+
+            {
+                add(firstInvoker);
+                add(secondInvoker);
+            }
+        });
+        given(directory.getUrl()).willReturn(url);
+        given(directory.getConsumerUrl()).willReturn(url);
+        given(directory.getConsumerUrl()).willReturn(url);
+        given(directory.getInterface()).willReturn(MenuService.class);
+
+        mergeableClusterInvoker = new MergeableClusterInvoker<MenuService>(directory);
+
+        // invoke
+        try {
+            Result result = mergeableClusterInvoker.invoke(invocation);
+            fail();
+            Assertions.assertNull(result.getValue());
+        } catch (RpcException expected) {
+            assertEquals(expected.getCode(), RpcException.NETWORK_EXCEPTION);
+        }
+    }
+
+    @Test
+    public void testGetMenuResultHasException() {
+
+        // setup
+        url = url.addParameter(MERGER_KEY, ".merge");
+
+        given(invocation.getMethodName()).willReturn("getMenu");
+        given(invocation.getParameterTypes()).willReturn(new Class<?>[]{});
+        given(invocation.getArguments()).willReturn(new Object[]{});
+        given(invocation.getObjectAttachments()).willReturn(new HashMap<>());
+        given(invocation.getInvoker()).willReturn(firstInvoker);
+
+        given(firstInvoker.getUrl()).willReturn(url.addParameter(GROUP_KEY, "first"));
+        given(firstInvoker.getInterface()).willReturn(MenuService.class);
+        given(firstInvoker.invoke(invocation)).willReturn(new AppResponse());
+        given(firstInvoker.isAvailable()).willReturn(true);
+
+        given(secondInvoker.getUrl()).willReturn(url.addParameter(GROUP_KEY, "second"));
+        given(secondInvoker.getInterface()).willReturn(MenuService.class);
+        given(secondInvoker.invoke(invocation)).willReturn(new AppResponse());
+        given(secondInvoker.isAvailable()).willReturn(true);
+
+        given(directory.list(invocation)).willReturn(new ArrayList() {
+
+            {
+                add(firstInvoker);
+                add(secondInvoker);
+            }
+        });
+        given(directory.getUrl()).willReturn(url);
+        given(directory.getConsumerUrl()).willReturn(url);
+        given(directory.getConsumerUrl()).willReturn(url);
+        given(directory.getInterface()).willReturn(MenuService.class);
+
+        mergeableClusterInvoker = new MergeableClusterInvoker<MenuService>(directory);
+
+        // invoke
+        try {
+            Result result = mergeableClusterInvoker.invoke(invocation);
+            fail();
+            Assertions.assertNull(result.getValue());
+        } catch (RpcException expected) {
+            Assertions.assertTrue(expected.getMessage().contains("Failed to invoke service"));
+        }
+    }
+
+    @Test
+    public void testGetMenuWithMergerDefault() {
+
+        // setup
+        url = url.addParameter(MERGER_KEY, "default");
+
+        given(invocation.getMethodName()).willReturn("getMenu");
+        given(invocation.getParameterTypes()).willReturn(new Class<?>[]{});
+        given(invocation.getArguments()).willReturn(new Object[]{});
+        given(invocation.getObjectAttachments()).willReturn(new HashMap<>());
+        given(invocation.getInvoker()).willReturn(firstInvoker);
+        // mock ApplicationModel
+        given(invocation.getModuleModel()).willReturn(moduleModel);
+        given(invocation.getModuleModel().getApplicationModel()).willReturn(ApplicationModel.defaultModel());
+
+
+        firstInvoker = (Invoker) Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[]{Invoker.class}, (proxy, method, args) -> {
+            if ("getUrl".equals(method.getName())) {
+                return url.addParameter(GROUP_KEY, "first");
+            }
+            if ("getInterface".equals(method.getName())) {
+                return MenuService.class;
+            }
+            if ("invoke".equals(method.getName())) {
+                return AsyncRpcResult.newDefaultAsyncResult(firstMenu, invocation);
+            }
+            return null;
+        });
+
+        secondInvoker = (Invoker) Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[]{Invoker.class}, (proxy, method, args) -> {
+            if ("getUrl".equals(method.getName())) {
+                return url.addParameter(GROUP_KEY, "second");
+            }
+            if ("getInterface".equals(method.getName())) {
+                return MenuService.class;
+            }
+            if ("invoke".equals(method.getName())) {
+                return AsyncRpcResult.newDefaultAsyncResult(secondMenu, invocation);
+            }
+            return null;
+        });
+
+        given(directory.list(invocation)).willReturn(new ArrayList() {
+
+            {
+                add(firstInvoker);
+                add(secondInvoker);
+            }
+        });
+        given(directory.getUrl()).willReturn(url);
+        given(directory.getConsumerUrl()).willReturn(url);
+        given(directory.getConsumerUrl()).willReturn(url);
+        given(directory.getInterface()).willReturn(MenuService.class);
+
+        mergeableClusterInvoker = new MergeableClusterInvoker<MenuService>(directory);
+
+        // invoke
+        try {
+            mergeableClusterInvoker.invoke(invocation);
+        } catch (RpcException exception) {
+            Assertions.assertTrue(exception.getMessage().contains("There is no merger to merge result."));
+        }
+    }
+
+    @Test
+    public void testDestroy() {
+        given(invocation.getMethodName()).willReturn("getMenu");
+        given(invocation.getParameterTypes()).willReturn(new Class<?>[]{});
+        given(invocation.getArguments()).willReturn(new Object[]{});
+        given(invocation.getObjectAttachments()).willReturn(new HashMap<>());
+        given(invocation.getInvoker()).willReturn(firstInvoker);
+
+        given(firstInvoker.getUrl()).willReturn(url.addParameter(GROUP_KEY, "first"));
+        given(firstInvoker.getInterface()).willReturn(MenuService.class);
+        given(firstInvoker.invoke(invocation)).willReturn(new AppResponse());
+
+        given(secondInvoker.getUrl()).willReturn(url.addParameter(GROUP_KEY, "second"));
+        given(secondInvoker.getInterface()).willReturn(MenuService.class);
+        given(secondInvoker.invoke(invocation)).willReturn(new AppResponse());
+
+        given(directory.list(invocation)).willReturn(new ArrayList() {
+
+            {
+                add(firstInvoker);
+                add(secondInvoker);
+            }
+        });
+        given(directory.getUrl()).willReturn(url);
+        given(directory.getConsumerUrl()).willReturn(url);
+        given(directory.getConsumerUrl()).willReturn(url);
+        given(directory.getInterface()).willReturn(MenuService.class);
+
+        mergeableClusterInvoker = new MergeableClusterInvoker<MenuService>(directory);
+        mergeableClusterInvoker.destroy();
+
+        assertFalse(firstInvoker.isAvailable());
+        assertFalse(secondInvoker.isAvailable());
+    }
 }