TableSpy
Enables spying the provided Table.
TableSpy is useful for writing unit tests, allowing you to stub sendable actions (e.g. Scans and Query), mock their behavior, and inspect their call history:
import { TableSpy } from 'dynamodb-toolbox/table/actions/spy'
const tableSpy = PokeTable.build(TableSpy)
// 🙌 Type-safe!
tableSpy.on(ScanCommand).resolve({ Items: mockedItems })
const { Items } = await PokeTable.build(ScanCommand)
  .options({ consistent: true })
  .send()
expect(Items).toStrictEqual(mockedItems) // ✅
const scanCount = tableSpy.sent(ScanCommand).count()
expect(scanCount).toBe(1) // ✅
// Reset history
tableSpy.reset()
// Stop spying
tableSpy.restore()
Non-mocked actions are sent as usual.
Methods
on(...)
(Action: SENDABLE_ACTION) => Stub<TABLE, SENDABLE_ACTION>
Enables stubbing a sendable action (see the stub section section for more details):
import { ScanCommand } from 'dynamodb-toolbox/table/actions/scan'
const scanStub = tableSpy.on(ScanCommand)
sent(...)
(Action: SENDABLE_ACTION) => Inspector<TABLE, SENDABLE_ACTION>
Enables inspecting a sendable action call history (see the inspector section section for more details):
import { ScanCommand } from 'dynamodb-toolbox/table/actions/scan'
const scanInspector = tableSpy.sent(ScanCommand)
reset()
() => Spy<TABLE>
Reset the call history for all actions:
expect(scanInspector.count()).toBe(1) // ✅
tableSpy.reset()
expect(scanInspector.count()).toBe(0) // ✅
// The method returns the spy, so you can chain a new stub:
tableSpy.reset().on(ScanCommand).resolve({ Items: [...] })
restore()
() => void
Stop spying the Table altogether:
// After this point, the spy is not able to intercept any action
tableSpy.restore()
Stub Methods
resolve(...)
(responseMock: Response<ACTION>) => Spy<TABLE>
Mocks the response of a sendable action .send() method:
// 🙌 Type-safe!
tableSpy.on(ScanCommand).resolve({ Items: mockedItems })
const { Items } = await PokeTable.build(ScanCommand).send()
expect(Items).toStrictEqual(mockedItems) // ✅
mock(...)
(mock: ((...args: Args<ACTION>) => Promisable<Response<ACTION>> | undefined)) => Spy<TABLE>
Mocks the implementation of a sendable action .send() method (synchronously or asynchronously), enabling you to return dynamic responses:
// 🙌 Type-safe!
tableSpy.on(ScanCommand).mock((entities, options) => {
  if (
    entities.length === 1 &&
    entities[0] === PokemonEntity
  ) {
    return { Items: mockedPokemons }
  }
})
const { Items } = await PokeTable.build(ScanCommand)
  .entities(PokemonEntity)
  .send()
expect(Items).toStrictEqual(mockedPokemons) // ✅
Returning undefined is possible and lets the action proceed as usual.
reject(...)
(error?: string | Error | AwsError) => Spy<TABLE>
Simulates an error during the execution of a sendable action .send() method:
- Any error
- Message
- AWS Error
tableSpy.on(ScanCommand).reject()
await expect(() =>
  PokeTable.build(ScanCommand).send()
).rejects.toThrow() // ✅
tableSpy.on(ScanCommand).reject('Fake error')
await expect(() =>
  PokeTable.build(ScanCommand).send()
).rejects.toThrow('Fake error') // ✅
tableSpy.on(ScanCommand).reject({
  Name: 'ServiceUnavailable',
  Code: '503',
  Message: 'Service is unable to handle request.',
  $fault: 'server',
  $service: 'DynamoDB'
})
await expect(() =>
  PokeTable.build(ScanCommand).send()
).rejects.toThrow({ Name: 'ServiceUnavailable' }) // ✅
Stub methods return the original spy, so you can easily chain them:
tableSpy
  .on(ScanCommand)
  .resolve({ Items: [...] })
  .on(QueryCommand)
  .reject('Some error')
Inspector methods
count()
() => number
Returns the number of times the action was sent:
tableSpy.on(ScanCommand).resolve({ Items: mockedItems })
const { Items } = await PokeTable.build(ScanCommand).send()
const count = tableSpy.sent(ScanCommand).count()
expect(count).toBe(1) // ✅
allArgs()
() => Args<ACTION>[]
Returns the arguments of the sendable action call history:
tableSpy.on(ScanCommand).resolve({})
await PokeTable.build(ScanCommand)
  .entities(PokemonEntity)
  .options({ consistent: true })
  .send()
await PokeTable.build(ScanCommand)
  .entities(TrainerEntity)
  .send()
const allArgs = tableSpy.sent(ScanCommand).allArgs()
expect(allArgs).toStrictEqual([
  // First call
  [PokemonEntity, { consistent: true }],
  // Second call
  [TrainerEntity, {}]
]) // ✅
args(...)
(index: number) => Args<ACTION>
Returns the arguments of the n-th action of the call history:
tableSpy.on(ScanCommand).resolve({})
await PokeTable.build(ScanCommand)
  .entities(PokemonEntity)
  .options({ consistent: true })
  .send()
await PokeTable.build(ScanCommand)
  .entities(TrainerEntity)
  .send()
const firstArgs = tableSpy.sent(ScanCommand).args(0)
expect(firstArgs).toStrictEqual([
  PokemonEntity,
  { consistent: true }
]) // ✅
Note that the index is zero-based.