aws / aws-appsync-community

The AWS AppSync community
https://aws.amazon.com/appsync
Apache License 2.0
506 stars 32 forks source link

import { util } from '@aws-appsync/utils' is empty object #363

Open naedx opened 1 month ago

naedx commented 1 month ago

I'm having some trouble using the @aws-appsync/utils (v1.8.0) in my Vitest tests. Given the following test script util is equal to {};


import { util } from '@aws-appsync/utils';
import { describe, expect, test } from 'vitest';

describe('Use AppSync @aws-appsync/utils', () => {

  test('util should not be empty', () => {
    console.log(util); // prints {}
    expect(util).not.toMatchObject({});
  });

  test('util.autoId() should be a function', () => {

    expect(util?.autoId).toBeTypeOf('function');

    const id = util.autoId();

    expect(id).toBeDefined();
  });

  test('util.time.nowISO8601() should produce a date', () => {

    expect(util.time.nowISO8601).toBeTypeOf('function');

    const t = util.time.nowISO8601();

    console.log(`Generated time: ${t}`);

    expect(t).toBeDefined();
  });

});

Importing Context from @aws-appsync/utils works as expected.

I've placed a full, self contained example here.

naedx commented 1 month ago

Stumbled upon this on r/aws by u/seedsseedsseeds also:

My team has been using AppSync for a couple years and for the most part, it's quite nice. Especially with JS resolvers (a pox upon VTL!) and the recently introduced AWS::Serverless::GraphqlApi type for AWS SAM, the developer experience has been greatly improved.

However, I've recently been working on improving our test suite, and have run aground upon a baffling obstacle -- @aws-appsync/utils doesn't allow for local testing!

Because the JS resolvers have a limited runtime, we're using util.nowEpochSeconds() to generate timestamps and util.dynamodb.toMapValues to marshall the dynamo commands, and the npm-installed version of the @aws-appsync/utils library has no implementation for these, only types.

I know AWS offers ways to test resolver definitions against the services itself, but these are simple resolvers, I just want a simple unit test harness to make sure the right values are defined.

Am I crazy, or missing something? Does anyone have a solution for how to test these things locally? Do I just have to waste the time to implement them myself and mock them?

They had to mock the library themselves. What is the correct solution?

nat-jones commented 1 month ago

I am having this issue trying to import { util } from '@aws-appsync/utils'; in a lambda function. Any update?

onlybakam commented 4 weeks ago

Hi. Javascript resolvers run on a runtime within the AppSync service. You can call AppSync's EvaluateCode API to test your resolver code. The @aws-appsync/utils package contains type definitions to help you implement your code.

See details here: https://docs.aws.amazon.com/appsync/latest/devguide/test-resolvers.html

naedx commented 3 weeks ago

Hi @onlybakam , thanks for your reply. Does that mean that the best solution is to (1) mock the functions if you need to test locally and (2) use EvaluateCode otherwise?

For anyone stumbling on this, you can mock the required functions in Vitest using:


// mock the required functioins from @aws-appsync/utils
vi.mock('@aws-appsync/utils', () => {

  const originalAppSyncUtils = require('@aws-appsync/utils');
  const { marshall } = require('@aws-sdk/util-dynamodb');
  const { v4 } = require('uuid');

  return {
    ...originalAppSyncUtils,
    util: {
      error: (msg: string | undefined) => { throw new Error(msg) },
      autoId: () => v4(),
      dynamodb: {
        toMapValues: (val: any) => { return marshall(val); }
      },
      time: {
        nowEpochSeconds: () => Math.floor(Date.now() / 1000),
        nowISO8601: () => new Date().toISOString()
      }
    }
  }
});

and you can use EvaluateCode approach like so:


//...

describe('Online/AWS Runtime Resolver Test', async () => {
  const client = new AppSync({ region: 'us-east-1' });
  const runtime = { name: 'APPSYNC_JS', runtimeVersion: '1.0.0' };
  const __dirname = path.resolve('amplify/data');

  test('Create todo resolver', async () => {
    const createTodoInput = {
      "content": `Test subject ${uuidV4()}`
    };

    const code = fs.readFileSync(__dirname + '/features/todo/todo.create.resolver.js', 'utf8')

    const contextJSON = createTestContextForEvaluate<{ input: typeof createTodoInput }>({ input: createTodoInput });

    const response = await client.evaluateCode({
      code,
      context: JSON.stringify(contextJSON),
      runtime,
      function: 'request'
    }).promise();

    const result = JSON.parse(response.evaluationResult!);

    expect(result?.key?.id?.S).toBeTypeOf('string');
    expect(result?.attributeValues?.content?.S).toEqual(contextJSON.arguments.input.content);
  });
});

//...

I've put a full example here https://github.com/naedx/amplify-playground/tree/dev/projects/amplify-appsync-vitest