aws-appsync-community icon indicating copy to clipboard operation
aws-appsync-community copied to clipboard

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

Open naedx opened this issue 1 year ago • 4 comments

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 avatar Jul 25 '24 16:07 naedx

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?

naedx avatar Jul 26 '24 13:07 naedx

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

nat-jones avatar Aug 08 '24 14:08 nat-jones

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

onlybakam avatar Aug 09 '24 18:08 onlybakam

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

naedx avatar Aug 12 '24 22:08 naedx