Validation Tests

Validation Tests

Sometimes we want the inputs to be configurable, but we also want to put constraints on those inputs or validate that the input is valid.

Suppose for the HitCounter construct we want to allow the user to specify the readCapacity on the DynamoDB table, but we also want to ensure the value is within a reasonable range. We can write a test to make sure that the validation logic works: pass in invalid values and see what happens.

First, add a readCapacity property to HitCounterProps:

Edit HitCounterProps.java

package com.myorg;

import software.amazon.awscdk.services.lambda.IFunction;

public interface HitCounterProps {
    // Public constructor for the props builder
    public static Builder builder() {
        return new Builder();
    }

    // The function for which we want to count url hits
    IFunction getDownstream();

    Number getReadCapacity();

    // The builder for the props interface
    public static class Builder {
        private IFunction downstream;
        private Number readCapacity;

        public Builder downstream(final IFunction function) {
            this.downstream = function;
            return this;
        }

        public Builder readCapacity(final Number readCapacity) {
          this.readCapacity = readCapacity;
          return this;
        }

        public HitCounterProps build() {
            if(this.downstream == null) {
                throw new NullPointerException("The downstream property is required!");
            }

            return new HitCounterProps() {
                @Override
                public IFunction getDownstream() {
                    return downstream;
                }

                @Override
                public Number getReadCapacity() {
                  return readCapacity;
                }
            };
        }
    }
}

Then update the DynamoDB table resource to add the readCapacity property.

Number readCapacity = (props.getReadCapacity() == null) ? 5 : props.getReadCapacity();

this.table = Table.Builder.create(this, "Hits")
    .partitionKey(Attribute.builder()
        .name("path")
        .type(AttributeType.STRING)
        .build())
    .encryption(TableEncryption.AWS_MANAGED)
    .readCapacity(readCapacity)
    .build();

Now add a validation which will throw an error if the readCapacity is not in the allowed range.

public class HitCounter extends Construct {
    private final Function handler;
    private final Table table;

    public HitCounter(final Construct scope, final String id, final HitCounterProps props) throws RuntimeException {
        super(scope, id);

        if (props.getReadCapacity() != null) {
          if (props.getReadCapacity().intValue() < 5 || props.getReadCapacity().intValue() > 20) {
            throw new RuntimeException("readCapacity must be greater than 5 or less than 20");
          }
        }

        ...

    }
}

Now lets add a test that validates the error is thrown.

    @Test
    public void testDynamoDBRaises() throws IOException {
        Stack stack = new Stack();

        Function hello = Function.Builder.create(stack, "HelloHandler")
            .runtime(Runtime.NODEJS_14_X)
            .code(Code.fromAsset("lambda"))
            .handler("hello.handler")
            .build();

        assertThrows(RuntimeException.class, () -> {
          new HitCounter(stack, "HelloHitCounter", HitCounterProps.builder()
              .downstream(hello)
              .readCapacity(1)
              .build());
        });
    }

Run the test.

$ mvn test

You should see an output like this:

$ mvn test

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.myorg.HitCounterTest
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.828 sec


Results :

Tests run: 4, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  10.148 s
[INFO] Finished at: 2021-11-01T12:54:45Z
[INFO] ------------------------------------------------------------------------