I am trying to match the arguments of functions calls for my unit tests.
The test code is below:
beforeEach(() => {
enrollmentOffer = offerReadModel(EnrollmentOfferStatuses.Accepted, false, undefined, true)
fakeSearcher = buildFakeReadModelSearcher()
...
})
afterEach(() => {
sinon.restore()
})
it('does the right query to get the latest child offer', async () => {
await expect(calculateRenewInfo(enrollmentOffer as EnrollmentOfferReadModel, true)).to.be.fulfilled
expect(fakeSearcher.filter).to.have.been.calledWithExactly({
offerType: { eq: OfferTypes.Child },
enrollmentOfferId: { eq: enrollmentOffer.id },
})
})
Currently this unit-test errs with:
22) calculateRenewInfo
does the right query to get the latest child offer:
AssertionError: expected filter to have been called with exact arguments {
offerType: { eq: 'child' },
enrollmentOfferId: { eq: '0a1a6e82-c2ca-48ec-9548-c4b5e4d26653' }
}
{ enrollmentOfferId: { eq: '0a1a6e82-c2ca-48ec-9548-c4b5e4d26653' } } {
offerType: { eq: 'child' },
enrollmentOfferId: { eq: '0a1a6e82-c2ca-48ec-9548-c4b5e4d26653' }
}
The reason the test fails, I believe, is because there are two calls. The first internal invocation is matching my test arguments. While the second, with arguments being { enrollmentOfferId: { eq: '0a1a6e82-c2ca-48ec-9548-c4b5e4d26653' } }
, is not matching it.
Normally, I would do something like: expect(sinonFake.firstCall).to.have.been.calledWith(...)
. This is however not working for me. For example, if I write expect(fakeSearcher.filter.firstCall).to.have.been.calledWithExactly(...)
, there is an error Property 'firstCall' does not exist on type '(filters: FilterFor<OfferReadModel>) => Searcher<OfferReadModel>'.
We build fakeSearcher
with:
export function buildFakeReadModelSearcher<TReadModel = unknown>(): Partial<Searcher<TReadModel>> {
const fakeSearcher: Partial<Searcher<TReadModel>> = {}
fakeSearcher.filter = sinon.fake.returns(fakeSearcher)
fakeSearcher.afterCursor = sinon.fake.returns(fakeSearcher)
fakeSearcher.paginatedVersion = sinon.fake.returns(fakeSearcher)
fakeSearcher.limit = sinon.fake.returns(fakeSearcher)
fakeSearcher.sortBy = sinon.fake.returns(fakeSearcher)
fakeSearcher.search = sinon.fake.resolves({ items: [] })
fakeSearcher.searchOne = sinon.fake.resolves(undefined)
return fakeSearcher
}
Any suggestions/ideas how I could only match the first call, with no or minimal changes to fakeSearcher
?
The problem is that buildFakeReadModelSearcher
says it returns a Partial<Searcher<TReadModel>>
so Typescript doesn't know that fakeSearcher.filter
is a sinon fake.
You could switch the function to return an object containing all those fakes + the fakeSearcher object as well and then use those fakes directly.
export function buildFakeReadModelSearcher<TReadModel = unknown>() {
const fakeSearcher: Partial<Searcher<TReadModel>> = {}
const filter = sinon.fake.returns(fakeSearcher)
const afterCursor = sinon.fake.returns(fakeSearcher)
const paginatedVersion = sinon.fake.returns(fakeSearcher)
const limit = sinon.fake.returns(fakeSearcher)
const sortBy = sinon.fake.returns(fakeSearcher)
const search = sinon.fake.resolves({ items: [] })
const searchOne = sinon.fake.resolves(undefined)
fakeSearcher.filter = filter
fakeSearcher.afterCursor = afterCursor
fakeSearcher.paginatedVersion = paginatedVersion
// ...etc.
return { fakeSearcher, filter, afterCursor, paginatedVersion, limit, sortBy, search, searchOne }
}
// Then use it like so:
expect(filter.firstCall).to.have.been.calledWithExactly({
offerType: { eq: OfferTypes.Child },
enrollmentOfferId: { eq: enrollmentOffer.id },
})