Search code examples
javatestinggenericsinterfaceanonymous-class

How to 'pass' methods to anonymous class


I am writing automated tests, and I want each to retry twice. So I wrote a method:

    public void retry(int retries, Retryable retryable) {
        for (int i = 0; i < retries; i++) {
            try {
                retryable.run();
                break;
            } catch (Exception e) {
                log.warn(WARN_TEXT, retryable, (i + 1), e);
                if (i == retries - 1) {
                    log.error(ERR_TEXT, retryable, retries, e);
                    retryable.handleException(e);
                    throw e;
                }
            }
        }
    }
public interface Retryable extends Runnable {

    void handleException(Exception e);
}

Now I have couple of test methods, let's write 3 here:

    @TestRailID(29828) // 1
    @Test(description = "Try saving a filter without a name.",
            groups = Group.PREPROD)
    public void tryCreatingNoNameFilter() {
        Retry.retry(2, new Retryable() {
            @Override
            public void handleException(Exception e) {
                log.error(TEST_RUN_FAIL, 2);
            }

            @Override
            public void run() {
                userTriesCreatingNoNameFilter();
            }
        });
    }

    @TestRailID(31391) // 2
    @Test(description = "Try saving a filter with too long name.",
            groups = Group.PREPROD)
    public void tryCreatingTooLongFilterName() {
        Retry.retry(2, new Retryable() {
            @Override
            public void handleException(Exception e) {
                log.error(TEST_RUN_FAIL, 2);
            }

            @Override
            public void run() {
                userTriesCreatingTooLongFilerName();
            }
        });
    }

    @TestRailID(29829) // 3
    @Test(description = "Create and save a new filter.",
            groups = Group.PREPROD)
    public void createNewFilter() {
        Retry.retry(2, new Retryable() {
            @Override
            public void handleException(Exception e) {
                log.error(TEST_RUN_FAIL, 2);
            }

            @Override
            public void run() {
                userTriesCreatingNewFilter();
            }
        });
    }

So we all can see that these methods differ only with run() method implemetation (single line). How can I do it without copy pasting that long blocks of code?

Thank you in advacne :)


Solution

  • To reduce the repetitive blocks and number of lines (and make this overall look cleaner), you could:

    1. Instead of extending Runnable, split up the exception handling and the run logic into two separate functional interfaces (see @FunctionalInterface):

      @FunctionalInterface
      interface ExceptionHandler {
        void handleException(Exception e);
      }
      

      Runnable is in fact already a functional interface, so you can stick to this.

    2. Then you can write these as lambdas:

      Retry.retry(
        2,
        () -> userTriesCreatingTooLongFilerName(), 
        exception -> log.error(TEST_RUN_FAIL, 2)
      );
      
    3. As your exception handling seems to be the same for all calls, define it once:

      var exceptionHandler = (ExceptionHandler) e -> log.error(TEST_RUN_FAIL, 2);
      
      Retry.retry(2, () -> userTriesCreatingNoNameFilter(), exceptionHandler);
      Retry.retry(2, () -> userTriesCreatingTooLongFilerName(), exceptionHandler);
      Retry.retry(2, () -> userTriesCreatingNewFilter(), exceptionHandler);
      // …
      

    Further, alternative options:

    1. Subclass your existing Retryable and pull up the common code.

    2. Add a default implementation to your existing interface with the common code.