Cucumber JVM: Mocking

In the previous cucumber-jvm post I introduced dependency injection, which makes it possible to use mocks during testing. Usually, it is a bad idea to introduce mocks in cucumber scenarios, because they are supposed to test the whole system as it is, however there are cases when mocking comes in handy: for example, a module or component of your system communicates with a 3rd party system. In this case, running the scenarios may be difficult, and the best option here is to mock or simulate that 3rd party system so that your application or product can still be tested. Technically, it is really easy to use mocks with cucumber-jvm, but there are certain limitations, which you’ll see at the end of this post.

Let’s say that the munger functionality in our SimpleTextMunger application is a 3rd party system, which communicates through the network, but that network is not reachable from our test system. Here is the current version of SimpleTextMunger.java:

public String execute(String sentence) {
  List words = sentenceHelper.split(sentence);
  for (int i = 0; i < words.size(); i++) {
    words.set(i, munger.munge(words.get(i)));
  }
  return sentenceHelper.join(words);
}

In order to mock the calls of the munger object we need something like this - I’m going use mockito as a mocking framework - SimpleTextMungerStepsdef.java:

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
// ...
when(munger.munge(inputWord)).thenReturn(mungedWord);
// ...

Mocks cannot do much, they are very simple. You have to define what they should return for a certain input. In order to use them effectively we need a different approach with the scenarios. *The original *scenarios - in simple_text_munger.feature - looked like this:

Background:
  Given I have an instance of my class

  Scenario Outline: It should munger a word properly
    When I call my method with <text>
    Then I receive <output>

    Examples:
      | input   | output  |
      | "a"     | "a"     |
      | "an"    | "an"    |
      | "and"   | "and"   |
      | "spice" | "scipe" |
  Scenario: It should process a sentence
    When I call my method with "And the spice must flow"
    Then I receive "And the scipe msut folw"

As you can see, all the existing scenarios test the munger, so we have to get rid of them, and have something like this in simple_text_munger.feature:

Background:
  Given I have a mocked munger which always returns "folw" for "flow"
    And I have an instance of my class

Scenario: It should process a sentence
  When I call my method with "flow flow"
  Then I receive "folw folw"

I admit that the original scenarios were more readable, but remember that the munger is mocked, because it is a 3rd party system and the scenario tells exactly what is happening: for a specific request return a specific result.

As you can see, it isn’t that hard, but there is a limitation here. In this article I mentioned that certain features of mockito - like using the @Mock annotation - require a @RunWith(MockitoJUnitRunner.class) annotation on the test case, but unfortunately, there is already a @RunWith annotation on the test case:

@RunWith(Cucumber.class)
@Cucumber.Options(features="classpath:simple_text_munger.feature")
public class SimpleTextMunger_Test {
}

In jUnit we cannot have more than one @RunWith annotation, and we need the one above for running cucumber-jvm test cases, so we cannot use those mockito features which require the presence of the MockitoJUnitRunner.

Nevertheless, mockito - and I assume the other mock frameworks as well - works fine with cucumber-jvm. The scenarios may look a bit weird, but there are cases when you cannot avoid using mocks. In the ruby version of cucumber there is no way to use mocks such as the mocks from RSpec. I don’t know why, but I have a theory: when you test the whole system then test the whole system, no exceptions. It is a nice idea, but as usual there are cases when you have to break some rules in order to achieve your goal. When I was doing web development, we used cucumber for testing. Since we couldn’t use RSpec mocks we implemented our own mocks and did almost the same as described above.

The only advice I can give you is to be really careful when you introduce a mock and cut a system out of your test suite. It may happen that the system changes, but your tests still relies on a mock which mimics an obsolete behavior of the 3rd party system - as it happened in our case.

The code, as usual, is available under the episode_4 branch of the repository on github. Next time I’m going to show how use cucumber-jvm for testing websites.


comments powered by Disqus