MRUNIT-119: Counter tests should support assertions that counters are not present. Contributed by Bertrand Dechoux.

git-svn-id: https://svn.apache.org/repos/asf/mrunit/trunk@1367010 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/hadoop/mrunit/MapDriver.java b/src/main/java/org/apache/hadoop/mrunit/MapDriver.java
index f058e03..d769a13 100644
--- a/src/main/java/org/apache/hadoop/mrunit/MapDriver.java
+++ b/src/main/java/org/apache/hadoop/mrunit/MapDriver.java
@@ -217,6 +217,12 @@
     super.withCounter(g, n, expectedValue);
     return this;
   }
+  
+  @Override
+  public MapDriver<K1, V1, K2, V2> withStrictCounterChecking() {
+    super.withStrictCounterChecking();
+    return this;
+  }
 
   public MapDriver<K1, V1, K2, V2> withOutputCopyingOrInputFormatConfiguration(
       Configuration configuration) {
diff --git a/src/main/java/org/apache/hadoop/mrunit/MapReduceDriver.java b/src/main/java/org/apache/hadoop/mrunit/MapReduceDriver.java
index 2de3e8a..6281852 100644
--- a/src/main/java/org/apache/hadoop/mrunit/MapReduceDriver.java
+++ b/src/main/java/org/apache/hadoop/mrunit/MapReduceDriver.java
@@ -428,6 +428,12 @@
     super.withCounter(g, n, e);
     return this;
   }
+  
+  @Override
+  public MapReduceDriver<K1, V1, K2, V2, K3, V3> withStrictCounterChecking() {
+    super.withStrictCounterChecking();
+    return this;
+  }
 
   /**
    * Returns a new MapReduceDriver without having to specify the generic types
diff --git a/src/main/java/org/apache/hadoop/mrunit/PipelineMapReduceDriver.java b/src/main/java/org/apache/hadoop/mrunit/PipelineMapReduceDriver.java
index d9b5d28..be797a4 100644
--- a/src/main/java/org/apache/hadoop/mrunit/PipelineMapReduceDriver.java
+++ b/src/main/java/org/apache/hadoop/mrunit/PipelineMapReduceDriver.java
@@ -291,6 +291,12 @@
     super.withCounter(g, n, e);
     return this;
   }
+  
+  @Override
+  public PipelineMapReduceDriver<K1, V1, K2, V2> withStrictCounterChecking() {
+    super.withStrictCounterChecking();
+    return this;
+  }
 
   /**
    * Expects an input of the form "key \t val" Forces the Reducer output types
diff --git a/src/main/java/org/apache/hadoop/mrunit/ReduceDriver.java b/src/main/java/org/apache/hadoop/mrunit/ReduceDriver.java
index fd2b7c0..0f49dde 100644
--- a/src/main/java/org/apache/hadoop/mrunit/ReduceDriver.java
+++ b/src/main/java/org/apache/hadoop/mrunit/ReduceDriver.java
@@ -227,6 +227,12 @@
     super.withCounter(g, n, e);
     return this;
   }
+  
+  @Override
+  public ReduceDriver<K1, V1, K2, V2> withStrictCounterChecking() {
+    super.withStrictCounterChecking();
+    return this;
+  }
 
   public ReduceDriver<K1, V1, K2, V2> withOutputCopyingOrInputFormatConfiguration(
       Configuration configuration) {
diff --git a/src/main/java/org/apache/hadoop/mrunit/TestDriver.java b/src/main/java/org/apache/hadoop/mrunit/TestDriver.java
index 3ebe85e..4555fca 100644
--- a/src/main/java/org/apache/hadoop/mrunit/TestDriver.java
+++ b/src/main/java/org/apache/hadoop/mrunit/TestDriver.java
@@ -22,6 +22,7 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -41,6 +42,7 @@
 
   protected List<Pair<K2, V2>> expectedOutputs;
 
+  private boolean strictCountersChecking = false;
   protected List<Pair<Enum<?>, Long>> expectedEnumCounters;
   protected List<Pair<Pair<String, String>, Long>> expectedStringCounters;
 
@@ -131,6 +133,19 @@
   }
 
   /**
+   * Change counter checking. After this method is called, the test will fail if
+   * an actual counter is not matched by an expected counter. By default, the
+   * test only check that every expected counter is there.
+   * 
+   * This mode allows you to ensure that no unexpected counters has been
+   * declared.
+   */
+  public TestDriver<K1, V1, K2, V2> withStrictCounterChecking() {
+    strictCountersChecking = true;
+    return this;
+  }
+
+  /**
    * @return The configuration object that will given to the mapper and/or
    *         reducer associated with the driver
    */
@@ -407,50 +422,76 @@
     LOG.error(msg);
     errors.add(msg);
   }
+  
+  /**
+   * Check counters.
+   */
+  protected void validate(final CounterWrapper counterWrapper) {
+    validateExpectedAgainstActual(counterWrapper);
+    validateActualAgainstExpected(counterWrapper);
+  }
 
   /**
-   * Check that passed counter do contain all expected counters with proper
+   * Same as {@link CounterWrapper#findCounterValues()} but for expectations.
+   */
+  private Collection<Pair<String, String>> findExpectedCounterValues() {
+    Collection<Pair<String, String>> results = new ArrayList<Pair<String, String>>();
+    for (Pair<Pair<String, String>,Long> counterAndCount : expectedStringCounters) {
+      results.add(counterAndCount.getFirst());
+    }
+    for (Pair<Enum<?>,Long> counterAndCount : expectedEnumCounters) {
+      Enum<?> first = counterAndCount.getFirst();
+      String groupName = first.getDeclaringClass().getName();
+      String counterName = first.name();
+      results.add(new Pair<String, String>(groupName, counterName));
+    }
+    return results;
+  }
+
+  /**
+   * Check that provided actual counters contain all expected counters with proper
    * values.
    * 
    * @param counterWrapper
    */
-  protected void validate(final CounterWrapper counterWrapper) {
+  private void validateExpectedAgainstActual(
+      final CounterWrapper counterWrapper) {
     boolean success = true;
     final List<String> errors = new ArrayList<String>();
-
+  
     // Firstly check enumeration based counters
     for (final Pair<Enum<?>, Long> expected : expectedEnumCounters) {
       final long actualValue = counterWrapper.findCounterValue(expected
           .getFirst());
-
+  
       if (actualValue != expected.getSecond()) {
         final String msg = "Counter "
             + expected.getFirst().getDeclaringClass().getCanonicalName() + "."
             + expected.getFirst().toString() + " have value " + actualValue
             + " instead of expected " + expected.getSecond();
         logError(errors, msg);
-
+  
         success = false;
       }
     }
-
+  
     // Second string based counters
     for (final Pair<Pair<String, String>, Long> expected : expectedStringCounters) {
       final Pair<String, String> counter = expected.getFirst();
-
+  
       final long actualValue = counterWrapper.findCounterValue(
           counter.getFirst(), counter.getSecond());
-
+  
       if (actualValue != expected.getSecond()) {
         final String msg = "Counter with category " + counter.getFirst()
             + " and name " + counter.getSecond() + " have value " + actualValue
             + " instead of expected " + expected.getSecond();
         logError(errors, msg);
-
+  
         success = false;
       }
     }
-
+  
     if (!success) {
       final StringBuilder buffer = new StringBuilder();
       buffer.append(errors.size()).append(" Error(s): ");
@@ -459,6 +500,34 @@
     }
   }
 
+  /**
+   * Check that provided actual counters are all expected.
+   * 
+   * @param counterWrapper
+   */
+  private void validateActualAgainstExpected(final CounterWrapper counterWrapper) {
+    if (strictCountersChecking) {
+      final List<String> errors = new ArrayList<String>();
+      Collection<Pair<String, String>> unmatchedCounters = counterWrapper.findCounterValues();
+      Collection<Pair<String, String>> findExpectedCounterValues = findExpectedCounterValues();
+      unmatchedCounters.removeAll(findExpectedCounterValues);
+      if(!unmatchedCounters.isEmpty()) {
+        for (Pair<String, String> unmatcherCounter : unmatchedCounters) {
+          final String msg = "Actual counter (\"" + unmatcherCounter.getFirst()
+              + "\",\"" + unmatcherCounter.getSecond()
+              + "\") was not found in expected counters";
+          logError(errors, msg);
+        }
+      }
+      if (!errors.isEmpty()) {
+        final StringBuilder buffer = new StringBuilder();
+        buffer.append(errors.size()).append(" Error(s): ");
+        formatValueList(errors, buffer);
+        fail(buffer.toString());
+      }
+    }
+  }
+
   protected static void formatValueList(final List<?> values,
       final StringBuilder sb) {
     sb.append("(");
diff --git a/src/main/java/org/apache/hadoop/mrunit/internal/counters/CounterWrapper.java b/src/main/java/org/apache/hadoop/mrunit/internal/counters/CounterWrapper.java
index b72b342..9e3b88a 100644
--- a/src/main/java/org/apache/hadoop/mrunit/internal/counters/CounterWrapper.java
+++ b/src/main/java/org/apache/hadoop/mrunit/internal/counters/CounterWrapper.java
@@ -19,6 +19,15 @@
 
 import static org.apache.hadoop.mrunit.internal.util.ArgumentChecker.returnNonNull;
 
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+import org.apache.hadoop.mapred.Counters.Group;
+import org.apache.hadoop.mapreduce.Counter;
+import org.apache.hadoop.mapreduce.CounterGroup;
+import org.apache.hadoop.mrunit.types.Pair;
+
 /**
  * Wrapper around Counters from both mapred and mapreduce packages so that we
  * can work with both counter classes in the same way.
@@ -81,4 +90,41 @@
       return mapreduce.findCounter(group, name).getValue();
     }
   }
+
+  public Collection<String> getGroupNames() {
+    if (mapred != null) {
+      return mapred.getGroupNames();
+    } else {
+      return mapreduce.getGroupNames();
+    }
+  }
+
+  /**
+   * @return the name of all counters
+   */
+  public Collection<Pair<String, String>> findCounterValues() {
+    final Collection<Pair<String, String>> counters = new LinkedList<Pair<String, String>>();
+    final Collection<String> groupNames = getGroupNames();
+    if (mapred != null) {
+      for (String groupName : groupNames) {
+        final Group group = mapred.getGroup(groupName);
+        collectCounters(counters, groupName, group.iterator());
+      }
+    } else {
+      for (String groupName : groupNames) {
+        final CounterGroup group = mapreduce.getGroup(groupName);
+        collectCounters(counters, groupName, group.iterator());
+      }
+    }
+    return counters;
+  }
+
+  /** Collect counters, same for both APIs **/
+  private void collectCounters(final Collection<Pair<String, String>> counters,
+      String groupName, Iterator<? extends Counter> iterator) {
+    while (iterator.hasNext()) {
+      String counter = iterator.next().getName();
+      counters.add(new Pair<String, String>(groupName, counter));
+    }
+  }
 }
diff --git a/src/main/java/org/apache/hadoop/mrunit/mapreduce/MapDriver.java b/src/main/java/org/apache/hadoop/mrunit/mapreduce/MapDriver.java
index 5f649da..b4df326 100644
--- a/src/main/java/org/apache/hadoop/mrunit/mapreduce/MapDriver.java
+++ b/src/main/java/org/apache/hadoop/mrunit/mapreduce/MapDriver.java
@@ -337,6 +337,12 @@
     super.withCounter(g, n, expectedValue);
     return this;
   }
+  
+  @Override
+  public MapDriver<K1, V1, K2, V2> withStrictCounterChecking() {
+    super.withStrictCounterChecking();
+    return this;
+  }
 
   /**
    * Returns a new MapDriver without having to specify the generic types on the
diff --git a/src/main/java/org/apache/hadoop/mrunit/mapreduce/MapReduceDriver.java b/src/main/java/org/apache/hadoop/mrunit/mapreduce/MapReduceDriver.java
index cf8d907..b46f757 100644
--- a/src/main/java/org/apache/hadoop/mrunit/mapreduce/MapReduceDriver.java
+++ b/src/main/java/org/apache/hadoop/mrunit/mapreduce/MapReduceDriver.java
@@ -453,6 +453,12 @@
     super.withCounter(g, n, e);
     return this;
   }
+  
+  @Override
+  public MapReduceDriver<K1, V1, K2, V2, K3, V3> withStrictCounterChecking() {
+    super.withStrictCounterChecking();
+    return this;
+  }
 
   /**
    * Returns a new MapReduceDriver without having to specify the generic types
diff --git a/src/main/java/org/apache/hadoop/mrunit/mapreduce/ReduceDriver.java b/src/main/java/org/apache/hadoop/mrunit/mapreduce/ReduceDriver.java
index a6f447e..024df72 100644
--- a/src/main/java/org/apache/hadoop/mrunit/mapreduce/ReduceDriver.java
+++ b/src/main/java/org/apache/hadoop/mrunit/mapreduce/ReduceDriver.java
@@ -325,6 +325,12 @@
     return this;
   }
   
+  @Override
+  public ReduceDriver<K1, V1, K2, V2> withStrictCounterChecking() {
+    super.withStrictCounterChecking();
+    return this;
+  }
+  
   /**
    * <p>Obtain Context object for furthering mocking with Mockito.
    * For example, causing write() to throw an exception:</p>
diff --git a/src/test/java/org/apache/hadoop/mrunit/TestMapDriver.java b/src/test/java/org/apache/hadoop/mrunit/TestMapDriver.java
index 7015d1f..bfd72cf 100644
--- a/src/test/java/org/apache/hadoop/mrunit/TestMapDriver.java
+++ b/src/test/java/org/apache/hadoop/mrunit/TestMapDriver.java
@@ -281,6 +281,50 @@
         .withCounter(MapperWithCounters.Counters.X, 1)
         .withCounter("category", "name", 1).runTest();
   }
+  
+  @Test
+  public void testWithCounterAndNoneMissing() throws IOException {
+    MapDriver<Text, Text, Text, Text> driver = MapDriver.newMapDriver();
+
+    driver.withMapper(new MapperWithCounters<Text, Text, Text, Text>())
+        .withInput(new Text("hie"), new Text("Hi"))
+        .withOutput(new Text("hie"), new Text("Hi"))
+        .withStrictCounterChecking()
+        .withCounter(MapperWithCounters.Counters.X, 1)
+        .withCounter("category", "name", 1).runTest();
+  }
+
+  @Test
+  public void testWithCounterAndEnumCounterMissing() throws IOException {
+    MapDriver<Text, Text, Text, Text> driver = MapDriver.newMapDriver();
+    
+    thrown
+        .expectAssertionErrorMessage("1 Error(s): (Actual counter ("
+            + "\"org.apache.hadoop.mrunit.TestMapDriver$MapperWithCounters$Counters\",\"X\")"
+            + " was not found in expected counters");
+
+    driver.withMapper(new MapperWithCounters<Text, Text, Text, Text>())
+        .withInput(new Text("hie"), new Text("Hi"))
+        .withOutput(new Text("hie"), new Text("Hi"))
+        .withStrictCounterChecking().withCounter("category", "name", 1)
+        .runTest();
+  }
+
+  @Test
+  public void testWithCounterAndStringCounterMissing() throws IOException {
+    MapDriver<Text, Text, Text, Text> driver = MapDriver.newMapDriver();
+    
+    thrown
+    .expectAssertionErrorMessage("1 Error(s): (Actual counter ("
+        + "\"category\",\"name\")"
+        + " was not found in expected counters");
+
+    driver.withMapper(new MapperWithCounters<Text, Text, Text, Text>())
+        .withInput(new Text("hie"), new Text("Hi"))
+        .withOutput(new Text("hie"), new Text("Hi"))
+        .withStrictCounterChecking()
+        .withCounter(MapperWithCounters.Counters.X, 1).runTest();
+  }
 
   @Test
   public void testWithFailedCounter() throws IOException {
diff --git a/src/test/java/org/apache/hadoop/mrunit/TestMapReduceDriver.java b/src/test/java/org/apache/hadoop/mrunit/TestMapReduceDriver.java
index 7c6cd88..8fc9ae0 100644
--- a/src/test/java/org/apache/hadoop/mrunit/TestMapReduceDriver.java
+++ b/src/test/java/org/apache/hadoop/mrunit/TestMapReduceDriver.java
@@ -391,6 +391,75 @@
         .withCounter("category", "count", 1).withCounter("category", "sum", 1)
         .runTest();
   }
+  
+  @Test
+  public void testWithCounterAndNoneMissing() throws IOException {
+    MapReduceDriver<Text, Text, Text, Text, Text, Text> driver = MapReduceDriver
+        .newMapReduceDriver();
+
+    driver
+        .withMapper(
+            new TestMapDriver.MapperWithCounters<Text, Text, Text, Text>())
+        .withInput(new Text("hie"), new Text("Hi"))
+        .withOutput(new Text("hie"), new Text("Hi"))
+        .withStrictCounterChecking()
+        .withCounter(TestMapDriver.MapperWithCounters.Counters.X, 1)
+        .withCounter("category", "name", 1)
+        .withReducer(
+            new TestReduceDriver.ReducerWithCounters<Text, Text, Text, Text>())
+        .withCounter(TestReduceDriver.ReducerWithCounters.Counters.COUNT, 1)
+        .withCounter(TestReduceDriver.ReducerWithCounters.Counters.SUM, 1)
+        .withCounter("category", "count", 1).withCounter("category", "sum", 1)
+        .runTest();
+  }
+
+  @Test
+  public void testWithCounterAndEnumCounterMissing() throws IOException {
+    MapReduceDriver<Text, Text, Text, Text, Text, Text> driver = MapReduceDriver
+        .newMapReduceDriver();
+
+    thrown
+        .expectAssertionErrorMessage("1 Error(s): (Actual counter ("
+            + "\"org.apache.hadoop.mrunit.TestMapDriver$MapperWithCounters$Counters\",\"X\")"
+            + " was not found in expected counters");
+
+    driver
+        .withMapper(
+            new TestMapDriver.MapperWithCounters<Text, Text, Text, Text>())
+        .withInput(new Text("hie"), new Text("Hi"))
+        .withOutput(new Text("hie"), new Text("Hi"))
+        .withStrictCounterChecking()
+        .withCounter("category", "name", 1)
+        .withReducer(
+            new TestReduceDriver.ReducerWithCounters<Text, Text, Text, Text>())
+        .withCounter(TestReduceDriver.ReducerWithCounters.Counters.COUNT, 1)
+        .withCounter(TestReduceDriver.ReducerWithCounters.Counters.SUM, 1)
+        .withCounter("category", "count", 1).withCounter("category", "sum", 1)
+        .runTest();
+  }
+
+  @Test
+  public void testWithCounterAndStringCounterMissing() throws IOException {
+    MapReduceDriver<Text, Text, Text, Text, Text, Text> driver = MapReduceDriver
+        .newMapReduceDriver();
+
+    thrown.expectAssertionErrorMessage("1 Error(s): (Actual counter ("
+        + "\"category\",\"name\")" + " was not found in expected counters");
+
+    driver
+        .withMapper(
+            new TestMapDriver.MapperWithCounters<Text, Text, Text, Text>())
+        .withInput(new Text("hie"), new Text("Hi"))
+        .withOutput(new Text("hie"), new Text("Hi"))
+        .withStrictCounterChecking()
+        .withCounter(TestMapDriver.MapperWithCounters.Counters.X, 1)
+        .withReducer(
+            new TestReduceDriver.ReducerWithCounters<Text, Text, Text, Text>())
+        .withCounter(TestReduceDriver.ReducerWithCounters.Counters.COUNT, 1)
+        .withCounter(TestReduceDriver.ReducerWithCounters.Counters.SUM, 1)
+        .withCounter("category", "count", 1).withCounter("category", "sum", 1)
+        .runTest();
+  }
 
   @Test
   public void testWithFailedCounter() throws IOException {
diff --git a/src/test/java/org/apache/hadoop/mrunit/TestPipelineMapReduceDriver.java b/src/test/java/org/apache/hadoop/mrunit/TestPipelineMapReduceDriver.java
index d56f955..f230ab8 100644
--- a/src/test/java/org/apache/hadoop/mrunit/TestPipelineMapReduceDriver.java
+++ b/src/test/java/org/apache/hadoop/mrunit/TestPipelineMapReduceDriver.java
@@ -166,6 +166,80 @@
         .withCounter("category", "count", 2).withCounter("category", "sum", 2)
         .runTest();
   }
+  
+  @Test
+  public void testWithCounterAndNoneMissing() throws IOException {
+    final PipelineMapReduceDriver<Text, Text, Text, Text> driver = PipelineMapReduceDriver.newPipelineMapReduceDriver();
+
+    driver.addMapReduce(
+        new TestMapDriver.MapperWithCounters<Text, Text, Text, Text>(),
+        new TestReduceDriver.ReducerWithCounters<Text, Text, Text, Text>());
+    driver.addMapReduce(
+        new TestMapDriver.MapperWithCounters<Text, Text, Text, Text>(),
+        new TestReduceDriver.ReducerWithCounters<Text, Text, Text, Text>());
+
+    driver.withInput(new Text("hie"), new Text("Hi"))
+        .withOutput(new Text("hie"), new Text("Hi"))
+        .withStrictCounterChecking()
+        .withCounter(TestMapDriver.MapperWithCounters.Counters.X, 2)
+        .withCounter("category", "name", 2)
+        .withCounter(TestReduceDriver.ReducerWithCounters.Counters.COUNT, 2)
+        .withCounter(TestReduceDriver.ReducerWithCounters.Counters.SUM, 2)
+        .withCounter("category", "count", 2).withCounter("category", "sum", 2)
+        .runTest();
+  }
+
+  @Test
+  public void testWithCounterAndEnumCounterMissing() throws IOException {
+    final PipelineMapReduceDriver<Text, Text, Text, Text> driver = PipelineMapReduceDriver.newPipelineMapReduceDriver();
+
+    thrown
+        .expectAssertionErrorMessage("1 Error(s): (Actual counter ("
+            + "\"org.apache.hadoop.mrunit.TestMapDriver$MapperWithCounters$Counters\",\"X\")"
+            + " was not found in expected counters");
+
+    driver.addMapReduce(
+        new TestMapDriver.MapperWithCounters<Text, Text, Text, Text>(),
+        new TestReduceDriver.ReducerWithCounters<Text, Text, Text, Text>());
+    driver.addMapReduce(
+        new TestMapDriver.MapperWithCounters<Text, Text, Text, Text>(),
+        new TestReduceDriver.ReducerWithCounters<Text, Text, Text, Text>());
+
+    driver.withInput(new Text("hie"), new Text("Hi"))
+        .withOutput(new Text("hie"), new Text("Hi"))
+        .withStrictCounterChecking()
+        .withCounter("category", "name", 2)
+        .withCounter(TestReduceDriver.ReducerWithCounters.Counters.COUNT, 2)
+        .withCounter(TestReduceDriver.ReducerWithCounters.Counters.SUM, 2)
+        .withCounter("category", "count", 2).withCounter("category", "sum", 2)
+        .runTest();
+  }
+
+  @Test
+  public void testWithCounterAndStringCounterMissing() throws IOException {
+    final PipelineMapReduceDriver<Text, Text, Text, Text> driver = PipelineMapReduceDriver.newPipelineMapReduceDriver();
+
+    thrown
+        .expectAssertionErrorMessage("1 Error(s): (Actual counter ("
+            + "\"category\",\"name\")"
+            + " was not found in expected counters");
+
+    driver.addMapReduce(
+        new TestMapDriver.MapperWithCounters<Text, Text, Text, Text>(),
+        new TestReduceDriver.ReducerWithCounters<Text, Text, Text, Text>());
+    driver.addMapReduce(
+        new TestMapDriver.MapperWithCounters<Text, Text, Text, Text>(),
+        new TestReduceDriver.ReducerWithCounters<Text, Text, Text, Text>());
+
+    driver.withInput(new Text("hie"), new Text("Hi"))
+        .withOutput(new Text("hie"), new Text("Hi"))
+        .withStrictCounterChecking()
+        .withCounter(TestMapDriver.MapperWithCounters.Counters.X, 2)
+        .withCounter(TestReduceDriver.ReducerWithCounters.Counters.COUNT, 2)
+        .withCounter(TestReduceDriver.ReducerWithCounters.Counters.SUM, 2)
+        .withCounter("category", "count", 2).withCounter("category", "sum", 2)
+        .runTest();
+  }
 
   @Test
   public void testWithFailedCounter() throws IOException {
diff --git a/src/test/java/org/apache/hadoop/mrunit/TestReduceDriver.java b/src/test/java/org/apache/hadoop/mrunit/TestReduceDriver.java
index 1e83c60..110954c 100644
--- a/src/test/java/org/apache/hadoop/mrunit/TestReduceDriver.java
+++ b/src/test/java/org/apache/hadoop/mrunit/TestReduceDriver.java
@@ -326,6 +326,69 @@
         .withCounter("category", "count", 1).withCounter("category", "sum", 2)
         .runTest();
   }
+  
+  @Test
+  public void testWithCounterAndNoneMissing() throws IOException {
+    final ReduceDriver<Text, Text, Text, Text> driver = ReduceDriver
+        .newReduceDriver();
+
+    final LinkedList<Text> values = new LinkedList<Text>();
+    values.add(new Text("a"));
+    values.add(new Text("b"));
+
+    driver.withReducer(new ReducerWithCounters<Text, Text, Text, Text>())
+        .withInput(new Text("hie"), values)
+        .withOutput(new Text("hie"), new Text("a"))
+        .withOutput(new Text("hie"), new Text("b")).withStrictCounterChecking()
+        .withCounter(ReducerWithCounters.Counters.COUNT, 1)
+        .withCounter(ReducerWithCounters.Counters.SUM, 2)
+        .withCounter("category", "count", 1).withCounter("category", "sum", 2)
+        .runTest();
+  }
+
+  @Test
+  public void testWithCounterAndEnumCounterMissing() throws IOException {
+    final ReduceDriver<Text, Text, Text, Text> driver = ReduceDriver
+        .newReduceDriver();
+
+    thrown
+        .expectAssertionErrorMessage("1 Error(s): (Actual counter ("
+            + "\"org.apache.hadoop.mrunit.TestReduceDriver$ReducerWithCounters$Counters\",\"COUNT\")"
+            + " was not found in expected counters");
+
+    final LinkedList<Text> values = new LinkedList<Text>();
+    values.add(new Text("a"));
+    values.add(new Text("b"));
+
+    driver.withReducer(new ReducerWithCounters<Text, Text, Text, Text>())
+        .withInput(new Text("hie"), values)
+        .withOutput(new Text("hie"), new Text("a"))
+        .withOutput(new Text("hie"), new Text("b")).withStrictCounterChecking()
+        .withCounter(ReducerWithCounters.Counters.SUM, 2)
+        .withCounter("category", "count", 1).withCounter("category", "sum", 2)
+        .runTest();
+  }
+
+  @Test
+  public void testWithCounterAndStringCounterMissing() throws IOException {
+    final ReduceDriver<Text, Text, Text, Text> driver = ReduceDriver
+        .newReduceDriver();
+
+    thrown.expectAssertionErrorMessage("1 Error(s): (Actual counter ("
+        + "\"category\",\"count\")" + " was not found in expected counters");
+
+    final LinkedList<Text> values = new LinkedList<Text>();
+    values.add(new Text("a"));
+    values.add(new Text("b"));
+
+    driver.withReducer(new ReducerWithCounters<Text, Text, Text, Text>())
+        .withInput(new Text("hie"), values)
+        .withOutput(new Text("hie"), new Text("a"))
+        .withOutput(new Text("hie"), new Text("b")).withStrictCounterChecking()
+        .withCounter(ReducerWithCounters.Counters.COUNT, 1)
+        .withCounter(ReducerWithCounters.Counters.SUM, 2)
+        .withCounter("category", "sum", 2).runTest();
+  }
 
   @Test
   public void testWithFailedEnumCounter() throws IOException {
diff --git a/src/test/java/org/apache/hadoop/mrunit/mapreduce/TestMapDriver.java b/src/test/java/org/apache/hadoop/mrunit/mapreduce/TestMapDriver.java
index df96dd3..ef7ae14 100644
--- a/src/test/java/org/apache/hadoop/mrunit/mapreduce/TestMapDriver.java
+++ b/src/test/java/org/apache/hadoop/mrunit/mapreduce/TestMapDriver.java
@@ -222,6 +222,50 @@
         .withCounter(MapperWithCounters.Counters.X, 1)
         .withCounter("category", "name", 1).runTest();
   }
+  
+  @Test
+  public void testWithCounterAndNoneMissing() throws IOException {
+    MapDriver<Text, Text, Text, Text> driver = MapDriver.newMapDriver();
+
+    driver.withMapper(new MapperWithCounters<Text, Text, Text, Text>())
+        .withInput(new Text("hie"), new Text("Hi"))
+        .withOutput(new Text("hie"), new Text("Hi"))
+        .withStrictCounterChecking()
+        .withCounter(MapperWithCounters.Counters.X, 1)
+        .withCounter("category", "name", 1).runTest();
+  }
+
+  @Test
+  public void testWithCounterAndEnumCounterMissing() throws IOException {
+    MapDriver<Text, Text, Text, Text> driver = MapDriver.newMapDriver();
+    
+    thrown
+        .expectAssertionErrorMessage("1 Error(s): (Actual counter ("
+            + "\"org.apache.hadoop.mrunit.mapreduce.TestMapDriver$MapperWithCounters$Counters\",\"X\")"
+            + " was not found in expected counters");
+
+    driver.withMapper(new MapperWithCounters<Text, Text, Text, Text>())
+        .withInput(new Text("hie"), new Text("Hi"))
+        .withOutput(new Text("hie"), new Text("Hi"))
+        .withStrictCounterChecking().withCounter("category", "name", 1)
+        .runTest();
+  }
+
+  @Test
+  public void testWithCounterAndStringCounterMissing() throws IOException {
+    MapDriver<Text, Text, Text, Text> driver = MapDriver.newMapDriver();
+    
+    thrown
+    .expectAssertionErrorMessage("1 Error(s): (Actual counter ("
+        + "\"category\",\"name\")"
+        + " was not found in expected counters");
+
+    driver.withMapper(new MapperWithCounters<Text, Text, Text, Text>())
+        .withInput(new Text("hie"), new Text("Hi"))
+        .withOutput(new Text("hie"), new Text("Hi"))
+        .withStrictCounterChecking()
+        .withCounter(MapperWithCounters.Counters.X, 1).runTest();
+  }
 
   @Test
   public void testWithFailedCounter() throws IOException {
diff --git a/src/test/java/org/apache/hadoop/mrunit/mapreduce/TestMapReduceDriver.java b/src/test/java/org/apache/hadoop/mrunit/mapreduce/TestMapReduceDriver.java
index f0519d8..f40441e 100644
--- a/src/test/java/org/apache/hadoop/mrunit/mapreduce/TestMapReduceDriver.java
+++ b/src/test/java/org/apache/hadoop/mrunit/mapreduce/TestMapReduceDriver.java
@@ -368,6 +368,72 @@
         .withCounter("category", "count", 1).withCounter("category", "sum", 1)
         .runTest();
   }
+  
+  @Test
+  public void testWithCounterAndNoneMissing() throws IOException {
+    MapReduceDriver<Text, Text, Text, Text, Text, Text> driver = MapReduceDriver
+        .newMapReduceDriver();
+
+    driver
+        .withMapper(
+            new TestMapDriver.MapperWithCounters<Text, Text, Text, Text>())
+        .withInput(new Text("hie"), new Text("Hi"))
+        .withStrictCounterChecking()
+        .withCounter(TestMapDriver.MapperWithCounters.Counters.X, 1)
+        .withCounter("category", "name", 1)
+        .withReducer(
+            new TestReduceDriver.ReducerWithCounters<Text, Text, Text, Text>())
+        .withCounter(TestReduceDriver.ReducerWithCounters.Counters.COUNT, 1)
+        .withCounter(TestReduceDriver.ReducerWithCounters.Counters.SUM, 1)
+        .withCounter("category", "count", 1).withCounter("category", "sum", 1)
+        .runTest();
+  }
+
+  @Test
+  public void testWithCounterAndEnumCounterMissing() throws IOException {
+    MapReduceDriver<Text, Text, Text, Text, Text, Text> driver = MapReduceDriver
+        .newMapReduceDriver();
+
+    thrown
+        .expectAssertionErrorMessage("1 Error(s): (Actual counter ("
+            + "\"org.apache.hadoop.mrunit.mapreduce.TestMapDriver$MapperWithCounters$Counters\",\"X\")"
+            + " was not found in expected counters");
+
+    driver
+        .withMapper(
+            new TestMapDriver.MapperWithCounters<Text, Text, Text, Text>())
+        .withInput(new Text("hie"), new Text("Hi"))
+        .withStrictCounterChecking()
+        .withCounter("category", "name", 1)
+        .withReducer(
+            new TestReduceDriver.ReducerWithCounters<Text, Text, Text, Text>())
+        .withCounter(TestReduceDriver.ReducerWithCounters.Counters.COUNT, 1)
+        .withCounter(TestReduceDriver.ReducerWithCounters.Counters.SUM, 1)
+        .withCounter("category", "count", 1).withCounter("category", "sum", 1)
+        .runTest();
+  }
+
+  @Test
+  public void testWithCounterAndStringCounterMissing() throws IOException {
+    MapReduceDriver<Text, Text, Text, Text, Text, Text> driver = MapReduceDriver
+        .newMapReduceDriver();
+
+    thrown.expectAssertionErrorMessage("1 Error(s): (Actual counter ("
+        + "\"category\",\"name\")" + " was not found in expected counters");
+
+    driver
+        .withMapper(
+            new TestMapDriver.MapperWithCounters<Text, Text, Text, Text>())
+        .withInput(new Text("hie"), new Text("Hi"))
+        .withStrictCounterChecking()
+        .withCounter(TestMapDriver.MapperWithCounters.Counters.X, 1)
+        .withReducer(
+            new TestReduceDriver.ReducerWithCounters<Text, Text, Text, Text>())
+        .withCounter(TestReduceDriver.ReducerWithCounters.Counters.COUNT, 1)
+        .withCounter(TestReduceDriver.ReducerWithCounters.Counters.SUM, 1)
+        .withCounter("category", "count", 1).withCounter("category", "sum", 1)
+        .runTest();
+  }
 
   @Test
   public void testWithFailedCounter() throws IOException {
diff --git a/src/test/java/org/apache/hadoop/mrunit/mapreduce/TestReduceDriver.java b/src/test/java/org/apache/hadoop/mrunit/mapreduce/TestReduceDriver.java
index bd0a6a2..b5b1089 100644
--- a/src/test/java/org/apache/hadoop/mrunit/mapreduce/TestReduceDriver.java
+++ b/src/test/java/org/apache/hadoop/mrunit/mapreduce/TestReduceDriver.java
@@ -321,6 +321,63 @@
         .withCounter("category", "count", 1).withCounter("category", "sum", 2)
         .runTest();
   }
+  
+  @Test
+  public void testWithCounterAndNoneMissing() throws IOException {
+    final ReduceDriver<Text, Text, Text, Text> driver = ReduceDriver
+        .newReduceDriver();
+
+    final LinkedList<Text> values = new LinkedList<Text>();
+    values.add(new Text("a"));
+    values.add(new Text("b"));
+
+    driver.withReducer(new ReducerWithCounters<Text, Text, Text, Text>())
+        .withInput(new Text("hie"), values).withStrictCounterChecking()
+        .withCounter(ReducerWithCounters.Counters.COUNT, 1)
+        .withCounter(ReducerWithCounters.Counters.SUM, 2)
+        .withCounter("category", "count", 1).withCounter("category", "sum", 2)
+        .runTest();
+  }
+
+  @Test
+  public void testWithCounterAndEnumCounterMissing() throws IOException {
+    final ReduceDriver<Text, Text, Text, Text> driver = ReduceDriver
+        .newReduceDriver();
+
+    thrown
+        .expectAssertionErrorMessage("1 Error(s): (Actual counter ("
+            + "\"org.apache.hadoop.mrunit.mapreduce.TestReduceDriver$ReducerWithCounters$Counters\",\"COUNT\")"
+            + " was not found in expected counters");
+
+    final LinkedList<Text> values = new LinkedList<Text>();
+    values.add(new Text("a"));
+    values.add(new Text("b"));
+
+    driver.withReducer(new ReducerWithCounters<Text, Text, Text, Text>())
+        .withInput(new Text("hie"), values).withStrictCounterChecking()
+        .withCounter(ReducerWithCounters.Counters.SUM, 2)
+        .withCounter("category", "count", 1).withCounter("category", "sum", 2)
+        .runTest();
+  }
+
+  @Test
+  public void testWithCounterAndStringCounterMissing() throws IOException {
+    final ReduceDriver<Text, Text, Text, Text> driver = ReduceDriver
+        .newReduceDriver();
+
+    thrown.expectAssertionErrorMessage("1 Error(s): (Actual counter ("
+        + "\"category\",\"count\")" + " was not found in expected counters");
+
+    final LinkedList<Text> values = new LinkedList<Text>();
+    values.add(new Text("a"));
+    values.add(new Text("b"));
+
+    driver.withReducer(new ReducerWithCounters<Text, Text, Text, Text>())
+        .withInput(new Text("hie"), values).withStrictCounterChecking()
+        .withCounter(ReducerWithCounters.Counters.COUNT, 1)
+        .withCounter(ReducerWithCounters.Counters.SUM, 2)
+        .withCounter("category", "sum", 2).runTest();
+  }
 
   @Test
   public void testWithFailedCounter() throws IOException {