0

Given a simple enum like this:

@AllArgsConstructor
@Getter
public enum PaymentMethod {

    CREDITCARD(1),
    PAYPAL(2),
    APPLE_PAY(3),
    GOOGLE_PAY(4);

    private final long id;
}

How can I write a JUnit test with AssertJ assertions which checks that the enum values and their fields are exactly as defined in the enum? So that in the end the test checks for each payment type:

  • Does payment type exist in the enum?
  • Does payment type X have id Y?

My current apporach works but feels a bit cumbersome. I'm pretty sure that AssertJ provides easier statements but I haven't found a solution yet.

class PaymentTypeTest {

    private static final Map<Long, PaymentType> expectedPaymentTypes = new HashMap<>();

    @BeforeAll
    static void beforeAll() {
        expectedPaymentTypes.put(1L, CREDITCARD);
        expectedPaymentTypes.put(2L, PAYPAL);
        expectedPaymentTypes.put(3L, APPLE_PAY);
        expectedPaymentTypes.put(4L, GOOGLE_PAY);    
    }

    @Test
    void should_contain_exact_values() {
        for (Map.Entry<Long, PaymentType> entry : expectedPaymentTypes.entrySet()) {
            final PaymentType paymentType = entry.getValue();
            assertThat(paymentType.getId()).isEqualTo(entry.getKey());
        }
    }
}```
Robert Strauch
  • 12,055
  • 24
  • 120
  • 192
  • 1
    The point is that this enum is used in multiple projects and the id must not change. If someone changed, for example, the id of `CREDITCARD` to `11`, then this must be detected by the test before it gets deployed to the depenendent projects which use this enum. Also, if someone removes a payment type... – Robert Strauch Jun 27 '22 at 06:11
  • 1
    I suppose the enum cordinal must not change. If two services are communicating then the ordinal should point to the same enum value. – Deepak Patankar Jun 27 '22 at 06:12
  • Possible duplicate of https://stackoverflow.com/questions/1079700/how-to-test-enum-types – mdoflaz Jun 27 '22 at 06:13
  • 1
    this does not look that bad :) however, you'd need to not forget to update the test when new enum value is added. to deal with this you might add another test that checks that the size of `expectedPaymentTypes` is the same as the number of enum values – vladtkachuk Jun 27 '22 at 06:51
  • 1
    Personally, I'm not a fan of separating the test data away from the test class, because when reading the (failing) test method, I then have to move to another place to see what is actually expected. So I would personally do an `assertThat( Arrays.stream(enums.values).map(value -> Pair.of(v.name(), value.getId()).containsExactly( Pair.of(...), Pair.of(...));` – Florian Schaetz Jun 27 '22 at 09:10

1 Answers1

2

JUnit 5 parameterized tests with a @CsvSource could help:

@ParameterizedTest
@CsvSource({
  "CREDITCARD, 1",
  "PAYPAL, 2",
  "APPLE_PAY, 3",
  "GOOGLE_PAY, 4"
})
void getId_should_return_expected_id(PaymentType paymentType, long id) {
  assertThat(paymentType.getId()).isEqualTo(id);
}

To make sure that all Enum values are tested, the mappings can be defined as a @MethodSource and one more test can enforce the full coverage:

@ParameterizedTest
@MethodSource("mappings")
void getId_should_return_expected_id(PaymentType paymentType, long id) {
  assertThat(paymentType.getId()).isEqualTo(id);
}

@Test
void all_values_should_be_tested() {
  List<PaymentType> values = mappings() // same mappings used above
    .map(arguments -> arguments.get()[0])
    .map(PaymentType.class::cast)
    .collect(Collectors.toList());

  assertThat(values).contains(PaymentType.values());
}

static Stream<Arguments> mappings() {
  return Stream.of(
    arguments(CREDITCARD, 1L),
    arguments(PAYPAL, 2L),
    arguments(APPLE_PAY, 3L),
    arguments(GOOGLE_PAY, 4L)
  );
}
Stefano Cordio
  • 1,687
  • 10
  • 20