Skip to content

Commit

Permalink
feat: Add examples with using aws-sdk-client-mock and aws sdk v2
Browse files Browse the repository at this point in the history
  • Loading branch information
phillip-le committed Sep 18, 2024
1 parent 37a0b72 commit 6020080
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"aws-sdk": "^2.1691.0",
"aws-sdk-client-mock": "^4.0.1",
"aws-sdk-client-mock-jest": "^4.0.1",
"aws-sdk-client-mock-vitest": "^4.0.0",
"dynamodb-admin": "^4.6.1",
"factory.ts": "^1.4.1",
"jsdom": "^25.0.0",
Expand Down
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { DynamoDB } from 'aws-sdk';

export const dynamoDbDocumentClient = new DynamoDB.DocumentClient();
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';

const dynamoDbClient = new DynamoDBClient();

export const dynamoDbDocumentClient =
DynamoDBDocumentClient.from(dynamoDbClient);
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { User } from '../types';
import { dynamoDbDocumentClient } from './aws-sdk-v2-client-global';
import { userRepository } from './user-repository-aws-sdk-v2-global';

vi.mock('./aws-sdk-v2-client-global');

describe('userRepository', () => {
beforeEach(() => {
vi.resetAllMocks();
});

it('should create a user', async () => {
const mockPutDynamoDb = vi.fn().mockImplementationOnce(() => ({
promise: vi.fn().mockResolvedValueOnce({}),
}));
vi.spyOn(dynamoDbDocumentClient, 'put').mockImplementation(mockPutDynamoDb);

await userRepository.createUser({ userId: '1', name: 'Alice' });

expect(dynamoDbDocumentClient.put).toHaveBeenCalledWith<
Parameters<typeof dynamoDbDocumentClient.put>
>({
TableName: 'Users',
Item: {
userId: '1',
name: 'Alice',
},
});
});

it('should get a user by ID', async () => {
const mockGetDynamoDb = vi.fn().mockImplementationOnce(() => ({
promise: vi
.fn()
.mockResolvedValueOnce({ Item: { userId: '1', name: 'Alice' } }),
}));
vi.spyOn(dynamoDbDocumentClient, 'get').mockImplementation(mockGetDynamoDb);

const user = await userRepository.getUserById('1');

expect(user).toEqual<User>({ userId: '1', name: 'Alice' });
expect(dynamoDbDocumentClient.get).toHaveBeenCalledWith({
TableName: 'Users',
Key: { userId: '1' },
});
});

it('should get all users with pagination', async () => {
const mockScanDynamoDb = vi
.fn()
.mockImplementationOnce(() => ({
promise: vi.fn().mockResolvedValueOnce({
Items: [{ userId: '1', name: 'Alice' }],
LastEvaluatedKey: { userId: '1' },
}),
}))
.mockImplementationOnce(() => ({
promise: vi.fn().mockResolvedValueOnce({
Items: [{ userId: '2', name: 'Bob' }],
}),
}));
vi.spyOn(dynamoDbDocumentClient, 'scan').mockImplementation(
mockScanDynamoDb,
);

const users = await userRepository.getUsers();

expect(users).toEqual<User[]>([
{
userId: '1',
name: 'Alice',
},
{
userId: '2',
name: 'Bob',
},
]);
expect(dynamoDbDocumentClient.scan).toHaveBeenCalledWith({
TableName: 'Users',
ExclusiveStartKey: {
userId: '1',
},
});
expect(dynamoDbDocumentClient.scan).toHaveBeenCalledWith({
TableName: 'Users',
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { DynamoDB } from 'aws-sdk';
import type { User, UserRepository } from '../types';
import { dynamoDbDocumentClient } from './aws-sdk-v2-client-global';

export const userRepository: UserRepository = {
createUser: async (user) => {
await dynamoDbDocumentClient
.put({
TableName: 'Users',
Item: user,
})
.promise();
},
getUserById: async (userId) => {
const result = await dynamoDbDocumentClient
.get({
TableName: 'Users',
Key: { userId },
})
.promise();

return result.Item as User;
},
getUsers: async () => {
const users: User[] = [];
let ExclusiveStartKey: DynamoDB.DocumentClient.Key | undefined = undefined;
do {
const response = await dynamoDbDocumentClient
.scan({
TableName: 'Users',
...(ExclusiveStartKey ? { ExclusiveStartKey } : {}),
})
.promise();

users.push(...(response.Items as User[]));

ExclusiveStartKey = response.LastEvaluatedKey;
} while (ExclusiveStartKey);

return users;
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {
DynamoDBDocumentClient,
GetCommand,
PutCommand,
ScanCommand,
} from '@aws-sdk/lib-dynamodb';
import { mockClient } from 'aws-sdk-client-mock';
import {
type CustomMatcher,
toHaveReceivedCommandWith,
toHaveReceivedNthCommandWith,
} from 'aws-sdk-client-mock-vitest';
import { expect } from 'vitest';
import type { User } from '../types';
import { userRepository } from './user-repository-bare-bones-client-global';

expect.extend({ toHaveReceivedCommandWith, toHaveReceivedNthCommandWith });

import 'vitest';

declare module 'vitest' {
// biome-ignore lint/suspicious/noExplicitAny: type of Assertion must match vitest
interface Assertion<T = any> extends CustomMatcher<T> {}
interface AsymmetricMatchersContaining extends CustomMatcher {}
}

describe('user repository bare bones client globally initialized', () => {
const mockDynamoDBDocumentClient = mockClient(DynamoDBDocumentClient);

afterEach(() => {
mockDynamoDBDocumentClient.reset();
});

it('should create a user', async () => {
mockDynamoDBDocumentClient.on(PutCommand).resolvesOnce({});

await userRepository.createUser({ userId: '1', name: 'Alice' });

expect(mockDynamoDBDocumentClient).toHaveReceivedCommandWith(PutCommand, {
TableName: 'Users',
Item: { userId: '1', name: 'Alice' },
});
});

it('should get user by id', async () => {
mockDynamoDBDocumentClient.on(GetCommand).resolvesOnce({
Item: { userId: '1', name: 'Alice' },
$metadata: {},
});

const user = await userRepository.getUserById('1');

expect(user).toEqual<User>({ userId: '1', name: 'Alice' });
expect(mockDynamoDBDocumentClient).toHaveReceivedCommandWith(GetCommand, {
TableName: 'Users',
Key: { userId: '1' },
});
});

it('should get all users', async () => {
mockDynamoDBDocumentClient
.on(ScanCommand)
.resolvesOnce({
Items: [{ userId: '1', name: 'Alice' }],
LastEvaluatedKey: {
userId: '1',
},
})
.resolvesOnce({
Items: [{ userId: '2', name: 'Bob' }],
});

const users = await userRepository.getUsers();

expect(users).toEqual<User[]>([
{ userId: '1', name: 'Alice' },
{ userId: '2', name: 'Bob' },
]);
expect(mockDynamoDBDocumentClient).toHaveReceivedNthCommandWith(
ScanCommand,
1,
{
TableName: 'Users',
},
);
expect(mockDynamoDBDocumentClient).toHaveReceivedNthCommandWith(
ScanCommand,
2,
{
TableName: 'Users',
ExclusiveStartKey: {
userId: '1',
},
},
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
GetCommand,
type GetCommandInput,
PutCommand,
type PutCommandInput,
paginateScan,
} from '@aws-sdk/lib-dynamodb';
import type { User, UserRepository } from '../types';
import { dynamoDbDocumentClient } from './aws-sdk-v3-bare-bones-client-global';

export const userRepository: UserRepository = {
createUser: async (user: User) => {
const input: PutCommandInput = {
TableName: 'Users',
Item: user,
};
await dynamoDbDocumentClient.send(new PutCommand(input));
},
getUserById: async (userId: string) => {
const input: GetCommandInput = {
TableName: 'Users',
Key: {
userId,
},
};
const response = await dynamoDbDocumentClient.send(new GetCommand(input));

return response.Item as User;
},
getUsers: async () => {
const paginator = paginateScan(
{ client: dynamoDbDocumentClient },
{ TableName: 'Users' },
);

const users: User[] = [];

for await (const page of paginator) {
users.push(...(page.Items as User[]));
}
return users;
},
};

0 comments on commit 6020080

Please sign in to comment.