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