Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I write unit tests for velocity templates?

Is this even possible?

Hello friends. I'm in the process of building an application using AWS AppSync + DynamoDB and I'm starting to have quite a large pile of resolver mapping templates, all which are written using the Apache Velocity Template Language (VTL).

The concern I'm starting to have is that these vtl files are quite critical to the application (since they define how data is retrieved) and a bug in one of the could wreak havoc. So like any critical part of a system... I would like to write some automated unit tests for them. But I haven't found much about others doing this.

  1. If you're using VTL (with AppSync or API Gateway), how do you test them?
  2. Is it even possible to write automated tests to velocity templates?
  3. Or am I going down the total wrong path and I should just be using Lambdas as my resolvers?

Thanks in advance!

like image 799
Maurice Avatar asked Jul 19 '19 16:07

Maurice


1 Answers

It took me a while to figure this out myself, but I found a good way to write unit tests for my VTL request and response templates. I used the amplify-appsync-simulator npm package's VelocityTemplate class. The only caveat I have seen so far is that you need to use $context in your VTL, the abbreviated $ctx is not recognized by the simulators VTL renderer. Check it out:

My VTL:

#set( $timeNow = $util.time.nowEpochMilliSeconds() )
{
    "version" : "2018-05-29",
    "operation" : "PutItem",
    "key": {
        "pk" : { "S" : "game#${util.autoId()}" },
        "sk" : { "S" : "meta#${timeNow}" }
    },
    "attributeValues" : {
        "players": { "L" : [
            { "M" : {   
                ## num and color added at start game             
                "player": $util.dynamodb.toDynamoDBJson($context.args.input.player)    
            }}                        
        ]},
        "createdBy": { "S": "${context.identity.sub}"},
        "gsipk": { "S": "${context.args.input.status}"},
        "gsisk": { "S": "${context.args.input.level}#${context.args.input.maxPlayers}"},
        "gsipk2": {"S": "game"},
        "turns": { "L": [] },
        "nextPlayerNum": { "N": 1 },
        "createdAt": { "N": ${timeNow} },
        "updatedAt": { "N": ${timeNow} }
    }
}

My test:

import { AmplifyAppSyncSimulator } from 'amplify-appsync-simulator'
import { VelocityTemplate } from "amplify-appsync-simulator/lib/velocity"
import { readFileSync } from 'fs'
import path from 'path';

const vtl = readFileSync(path.join(__dirname, '..', 'addGame-request-mapping-template.vtl'))
const template = {
  content: vtl
}
const velocity = new VelocityTemplate(template, new AmplifyAppSyncSimulator)

describe('valid user and request', () => {

  // This is the graphql input
  const validContext = {
    arguments: {
      input: {
        player: 'player#1234',
        maxPlayers: 4,
        status: 'new',
        level: 7
      }
    },
    source: {}
  }

  // This is a logged in user with a JWT
  const requestContext = {
    requestAuthorizationMode: 'OPENID_CONNECT',
    jwt: {
      sub: 'abcd1234'
    }
  }

  const info = {
    fieldNodes: []
  }

  it('works', () => {
    const result = velocity.render(validContext, requestContext, info)
    expect(result).toEqual({
      result: {
        version: "2018-05-29",
        operation: "PutItem",
        key: {
          pk: { S: expect.stringMatching(/^game#[a-f0-9-]*$/) },
          sk: { S: expect.stringMatching(/^meta#[0-9]*$/)}
        },
        attributeValues: {
          players: {
            L: [
              { M: { player: { S: validContext.arguments.input.player }}}
            ]
          },
          createdBy: { S: requestContext.jwt.sub },
          gsipk: { S: validContext.arguments.input.status },
          gsisk: { S: `${validContext.arguments.input.level}#${validContext.arguments.input.maxPlayers}`},
          gsipk2: { S: 'game' },
          turns: { L: [] },
          nextPlayerNum: { N: 1 },
          createdAt: { N: expect.any(Number) },
          updatedAt: { N: expect.any(Number) }
        }
      },
      stash: {},
      errors: [],
      isReturn: false
    })
  })
})
like image 80
DaKaZ Avatar answered Oct 11 '22 12:10

DaKaZ