Search code examples
javatestingconcordion

Workaround for Java Concordion substring ("contains")


As i see, Concordion test framework makes only exact strings/numbers matching.

Is there any workaround to test the result contains some substring?

For instance, i test a service returns an error in format ERROR 123, and the test should be valid for any error number.


Solution

  • There's a number of answers depending on the desired readability of the output and the amount of effort you want to put in.

    1) Easiest is to use assert-true to check for the substring, eg:

    The service [returns](- "#result=callService()") an [ERROR](- "c:assert-true=containsSubstring(#result, #TEXT)")
    

    with fixture:

    @RunWith(ConcordionRunner.class)
    public class StackOverflow {
    
        public String callService() {
            // call your service here
        }
    
        public boolean containsSubstring(String input, String check) {
            return input.contains(check);
        }
    }
    

    The downside of this is that, on failure, the error message will just show:

    message showing '== false'

    2) Same fixture as 1, with containsSubstring throwing an exception on failure. Concordion would then show the failure as a stack trace:

    public boolean containsSubstring(String input, String check) {
        if (!input.contains(check)) {
            throw new AssertionError("'" + input + "' does not contain '" + check + "'");
        }
        return true;
    }
    

    On failure this shows:

    stack trace showing '200 OK' does not contain 'ERROR'

    3) Use assertEquals to check:

    The service [returns](- "#result=callService()") an [ERROR](- "?=contains(#result, #TEXT)")
    

    with fixture:

    public String contains(String input, String check) {
        if (!input.contains(check)) {
            return input;
        }
        return check;
    }
    

    This shows the "best" error message:message showing 'ERROR' in strikethrough, then '200 OK'

    4) You could write an extension, which would save you from defining such a contains method in every fixture (or inheriting them all from one superclass fixture):

    Spec:

    The service [returns](- "#result=callService()") an [ERROR](- "cx:isError=#result")
    

    Fixture:

    @RunWith(ConcordionRunner.class)
    @ConcordionOptions(declareNamespaces={"cx", "urn:error-extension:2017"})
    @Extensions(ErrorExtension.class)
    public class StackOverflow {
    
        public String callService() {
            // call your service here
        }
    }
    

    Extension:

    public class ErrorExtension implements ConcordionExtension {
        private List<AssertListener> listeners = new ArrayList<AssertListener>();
    
        public void addAssertEqualsListener(AssertListener listener) {
            listeners.add(listener);
        }
    
        public void removeAssertEqualsListener(AssertListener listener) {
            listeners.remove(listener);
        }
    
        @Override
        public void addTo(ConcordionExtender concordionExtender) {
            concordionExtender.withCommand("urn:error-extension:2017", "isError", new AbstractCommand() {
                @Override
                public void verify(CommandCall commandCall, Evaluator evaluator, ResultRecorder resultRecorder) {
                    Check.isFalse(commandCall.hasChildCommands(), "Nesting commands inside an 'isError' is not supported");
    
                    Element element = commandCall.getElement();
    
                    String actual = (String) evaluator.evaluate(commandCall.getExpression());
    
                    if (actual.contains("ERROR")) {
                        resultRecorder.record(Result.SUCCESS);
                        announceSuccess(element);
                    } else {
                        resultRecorder.record(Result.FAILURE);
                        announceFailure(element, "String containing ERROR", actual);
                    }
                }
            });
            listeners.add(new AssertResultRenderer());
        }
    
        private void announceSuccess(Element element) {
            for (AssertListener listener : listeners) {
                listener.successReported(new AssertSuccessEvent(element));
            }
        }
    
        private void announceFailure(Element element, String expected, Object actual) {
            for (AssertListener listener : listeners) {
                listener.failureReported(new AssertFailureEvent(element, expected, actual));
            }
        }
    }
    

    which also shows the "best" error message:message showing 'ERROR' in strikethrough, then '200 OK'