javaerror-handlingcucumbercucumber-java

What is the correct way of reporting custom exceptions happening when decoding `DataTable` in _Cucumber_?


I am using Cucumber 7.18 with Java 21. My test engineer is agreeable enough to write tests scenarios. I return, I wish to make error messages as readable as possible.

I have troubles showing up the error message when it happens in DataTable transformations.

For example, say that the test contains the table below. There is an error in column CODE at row number 3, because it should always be integers:

    Given My system produces the following entries:
      | NAME     | ORC |          CODE | EPT | EFL | TIME   |
      | AFR89YB  | A   |          4455 | DEP | 100 | -00:10 |
      | AFR89YC  | B   |          4455 | DEP | 100 | -00:10 |
      | AFR89YD  | C   | Illegal value | DEP | 100 | -00:10 |
      | ...      | ... |           ... | ... | ... | ...    |

To decode the table I register a @DataTableType:

    @DataTableType
    public MySystemEntry mySystemEntry(Map<String, String> entry) {
        return MySystemEntry.builder()
                .entry(entry)
                .build();
    }

And, in the builder, I got plenty of validation rules, like this one:

public class MySystemEntry {
   ...
   public static Builder builder() {
      return new Builder();
   }

   ...
   public static Builder {
      ...
      public Builder entry(Map<String, String> entry) {
          ...
          ...
          String value = entry.get("CODE");
          if (notANumber(value)) {
              throw new IllegalArgumentException("Value " + value + " is illegal for Column 'CODE'.");
          }
          ...
      }
      ...
   }
}

When values are correct, everything is good. When values are incorrect, I can see my special exception fire up by setting up a break point in the throw new IllegalArgumentException. Higher up in the call stack it becomes a InvocationTargetException, still keeping the cause. Higher up it becomes a CucumberInvocationTargetException, but the cause is placed in a special invocationTargetException property. Finally, it becomes a CucumberDataTableException, and the special property invocationTargetException is ignored (and therefore lost).

As expected, the test fails. My problem is that the error log is quite unhelpful as it doesn't show the detailed error message specifically crafted my me:

Step failed
io.cucumber.datatable.CucumberDataTableException: 'java.util.List<com.my-application.MySystemEntry>' could not transform
| NAME     | ORC |          CODE | EPT | EFL | TIME   |
| AFR89YB  | A   |          4455 | DEP | 100 | -00:10 |
| AFR89YC  | B   |          4455 | DEP | 100 | -00:10 |
| AFR89YD  | C   | Illegal value | DEP | 100 | -00:10 |
| ...      | ... |           ... | ... | ... | ...    |

    at io.cucumber.datatable.DataTableType.transform(DataTableType.java:158)
    ...
    ...
    at ✽.Given My system produces the following entries: (file:///D:/dev/java/my-application/src/test/resources/features/my-system-works.feature:26)
Caused by: io.cucumber.core.backend.CucumberInvocationTargetException
    at io.cucumber.java.Invoker.doInvoke(Invoker.java:73)
    ...
    ...

For me, this is a bit of an annoyance - if I can't see the problem, I set a break point and pinpoint exactly what's amiss. But my test engineer is completely lost with the error message. He has to check every value, in every column and row, until he finds the mismatch.

What is the correct way of reporting custom exceptions happening when decoding DataTable in Cucumber?

EDIT

Following suggestion of @m-p-korstanje, I forked cucumber-java-skeleton and made a working example here:


Solution

  • You'll get a nice stack trace if you let Cucumber do the conversion for you by declaring the type you want in the step definition method.

        @When("I mix the following ingredients")
        public void i_wait_hour(List<Ingredient> ingredients) {
            // use ingredients here
        }
    
    Scenario: a few cukes                  # io/cucumber/skeleton/belly.feature:3
      Given I have 42 cukes in my belly    # io.cucumber.skeleton.StepDefinitions.I_have_cukes_in_my_belly(int)
      When I mix the following ingredients # io.cucumber.skeleton.StepDefinitions.i_wait_hour(java.util.List<io.cucumber.skeleton.StepDefinitions$Ingredient>)
        | NAME  | QUANTITY | UNITS |
        | Flour | 1        | KG    |
        | Water | 0.65     | L     |
        | Salt  | 0.08     | KG    |
          java.lang.IllegalArgumentException: This message is never shown during test execution
        at io.cucumber.skeleton.StepDefinitions.mySystemEntry(StepDefinitions.java:29)
        at ✽.I mix the following ingredients(classpath:io/cucumber/skeleton/belly.feature:6)