### Question:
RemoveNodeTask extends AbstractGroupConfigChangeTask { @Override public synchronized void onLogCommitted() { if (state != State.GROUP_CONFIG_APPENDED) { throw new IllegalStateException("log committed before log appended"); } setState(State.GROUP_CONFIG_COMMITTED); context.removeNode(nodeId); notify(); } RemoveNodeTask(GroupConfigChangeTaskContext context, NodeId nodeId); @Override boolean isTargetNode(NodeId nodeId); @Override synchronized void onLogCommitted(); @Override String toString();  }### Answer:
@Test public void testNormal() throws InterruptedException, ExecutionException { WaitableGroupConfigChangeTaskContext taskContext = new WaitableGroupConfigChangeTaskContext(); RemoveNodeTask task = new RemoveNodeTask( taskContext, NodeId.of("D") ); Future<GroupConfigChangeTaskResult> future = taskExecutor.submit(task); taskContext.awaitLogAppended(); task.onLogCommitted(); Assert.assertEquals(GroupConfigChangeTaskResult.OK, future.get()); }
@Test(expected = IllegalStateException.class) public void testOnLogCommittedLogNotAppended() { RemoveNodeTask task = new RemoveNodeTask( new WaitableGroupConfigChangeTaskContext(), NodeId.of("D") ); task.onLogCommitted(); }