Skip to the content
OrielDocs
0.1.0-alpha.10GitHub

Testing

Fakes

A fake stands in for a service in your tests: it records what your code asks of it, does nothing else, and lets you check what was asked. Oriel has fakes for the bus, events, mail, notifications, queues and storage.

Introduction#

Mail.fake() and the other fake() methods put a fake in place of the service, for the rest of the test, and return it. Check your code's work on the fake they return:

tests/Feature/OrderShipment.test.ts
import { Mail } from '@orieljs/framework'import { test } from 'vitest'import { OrderShipped } from '../../app/Mail/OrderShipped'import { TestCase } from '../TestCase'const t = TestCase.boot()test('orders can be shipped', async () => {  const mail = Mail.fake()  await t.post('/orders/1/shipment')  mail.assertSent(OrderShipped)})

Each test has a new application, so a fake ends with its test.

Bus fake#

Bus.fake() records the jobs your code dispatches, and runs none of them:

tests/Feature/Podcasts.test.ts
import { Bus } from '@orieljs/framework'import { RefreshDatabase } from '@orieljs/framework/testing'import { test } from 'vitest'import { OptimizePodcast } from '../../app/Jobs/OptimizePodcast'import { ProcessPodcast } from '../../app/Jobs/ProcessPodcast'import { TestCase } from '../TestCase'const t = TestCase.boot({ uses: [RefreshDatabase] })test('podcasts can be published', async () => {  const bus = Bus.fake()  await t.post('/podcasts', { title: 'Episode 1' })  bus.assertDispatched(ProcessPodcast, (job) => job.podcast.title === 'Episode 1')  bus.assertDispatched(ProcessPodcast, 1)  bus.assertNotDispatched(OptimizePodcast)})

The second argument is a function that receives each job and returns true for the one you expect, or a number of dispatches. Give fake() a list of jobs to fake only those, and the others run as usual: Bus.fake([ProcessPodcast]). BusFake.fakeExcept([ShipOrder]), from @orieljs/framework/testing, fakes all but those.

AssertionPasses when
assertDispatched(job, filterOrCount?), assertDispatchedTimes(job, times)The job was dispatched, matching the filter, or that many times
assertNotDispatched(job, filter?), assertNothingDispatched()It wasn't, or no job was
assertDispatchedSync(job), assertNotDispatchedSync(job)It was dispatched with dispatchSync(), or wasn't
assertDispatchedAfterResponse(job), assertNotDispatchedAfterResponse(job)It was dispatched to run after the response, or wasn't
assertDispatchedWithoutChain(job)It was dispatched with no chain

bus.dispatched(ProcessPodcast) returns the dispatched jobs, to check them with Vitest's expect(), and dispatchedSync() and dispatchedAfterResponse() the others.

Job chains#

assertChained() checks a chain, by class or by instance:

bus.assertChained([ProcessPodcast, OptimizePodcast, PublishPodcast])

An instance in the list matches a job of its class with the same fields.

Job batches#

assertBatched() receives each batch, with its name and jobs, and passes when the function returns true for one:

bus.assertBatched((batch) => batch.name === 'import-csv' && batch.jobs.length === 10)

assertBatchCount(count) checks the number of batches, and assertNothingBatched() that there are none.

Event fake#

Event.fake() records the events your code dispatches, and runs none of their listeners:

import { OrderShipped } from './app/Events/OrderShipped'const events = Event.fake()// Ship the order...events.assertDispatched(OrderShipped, (event) => event.orderId === 1)events.assertDispatchedTimes(OrderShipped, 1)events.assertNotDispatched('order.cancelled')

assertNothingDispatched() checks that no event was dispatched at all.

assertListening(OrderShipped, SendShipmentNotification) checks that a listener is registered for an event, and dispatched(event) returns the dispatched events.

EventFake.fakeExcept([OrderCreated]), from @orieljs/framework/testing, fakes every event but those.

Mail fake#

Mail.fake() records each mail your code sends or queues, and sends nothing:

import { OrderShipped } from './app/Mail/OrderShipped'const mail = Mail.fake()// Ship the order...mail.assertSent(OrderShipped, (mailable, record) => record.hasTo('taylor@example.com'))mail.assertSent(OrderShipped, 1)mail.assertNotQueued(OrderShipped)

The filter receives the mailable and its record, which has hasTo(), hasCc(), hasBcc() and hasSubject(). A mailable that would be queued counts as queued, not sent.

AssertionPasses when
assertSent(mailable, filterOrCount?), assertSentTimes(mailable, times)The mail was sent, matching the filter, or that many times
assertNotSent(mailable), assertNothingSent(), assertSentCount(count)It wasn't sent, no mail was, or that many were
assertQueued(mailable, filterOrCount?), assertNotQueued(mailable)It was queued, or wasn't
assertNothingQueued(), assertQueuedCount(count)No mail was queued, or that many were
assertOutgoingCount(count), assertNothingOutgoing()That many mails were sent or queued, or none

mail.sent(OrderShipped) and mail.queued(OrderShipped) return the mailables. Nothing is rendered, so the fake doesn't check your views.

Notification fake#

Notification.fake() records each notification your code sends, and delivers none:

import { User } from './app/Models/User'import { InvoicePaid } from './app/Notifications/InvoicePaid'const notifications = Notification.fake()const user = await User.findOrFail(1)// Pay the invoice...notifications.assertSentTo(user, InvoicePaid, (notification, channels) => channels.includes('mail'))notifications.assertNotSentTo(user, InvoicePaid, (notification) => notification.amount === '0')notifications.assertCount(1)

The filter receives the notification, its channels and the notifiable. assertSentTo() also takes a list of notifiables, which must each have the notification.

AssertionPasses when
assertSentTo(notifiable, notification, filterOrCount?)The notifiable was sent the notification
assertSentToTimes(notifiable, notification, times)It was sent it that many times
assertNotSentTo(notifiable, notification, filter?), assertNothingSentTo(notifiable)It wasn't sent it, or anything
assertSentTimes(notification, times), assertCount(count)That many were sent, of that class or of all
assertNothingSent()No notification was sent
assertSentOnDemand(notification, filterOrCount?)It was sent to a route, with Notification.route()

notifications.sent(user, InvoicePaid) returns the notifications the user was sent.

Queue fake#

Queue.fake() records the jobs pushed on the queue, and runs none of them. Unlike Bus.fake(), it sees the queue each job is pushed on:

import { ProcessPodcast } from './app/Jobs/ProcessPodcast'const queue = Queue.fake()// Publish the podcast...queue.assertPushed(ProcessPodcast, 1)queue.assertPushedOn('audio', ProcessPodcast)queue.assertPushed(ProcessPodcast, (job, queueName) => queueName === 'audio')
AssertionPasses when
assertPushed(job, filterOrCount?), assertPushedTimes(job, times)The job was pushed, matching the filter, or that many times
assertPushedOn(queue, job, filter?)It was pushed on that queue
assertNotPushed(job, filter?), assertNothingPushed()It wasn't, or no job was
assertCount(count)That many jobs were pushed
assertPushedWithChain(job, chain), assertPushedWithoutChain(job)It was pushed with that chain after it, or with none

queue.pushed(ProcessPodcast) returns the pushed jobs. Give fake() a list of jobs to fake only those.

Storage fake#

Storage.fake() puts a disk in memory in place of one of yours, so a test writes no files, and returns it:

tests/Feature/Avatar.test.ts
import { Storage } from '@orieljs/framework'import { FakeFile } from '@orieljs/framework/testing'import { test } from 'vitest'import { TestCase } from '../TestCase'const t = TestCase.boot()test('albums can be uploaded', async () => {  const disk = Storage.fake('photos')  await t.post('/photos', { photos: [FakeFile.image('photo1.jpg'), FakeFile.image('photo2.jpg')] })  await disk.assertExists(['photo1.jpg', 'photo2.jpg'])  await disk.assertMissing('missing.jpg')  await disk.assertCount('', 2)})

With no name, it fakes the default disk. The assertions read the disk, so they are async: await each one.

AssertionPasses when
assertExists(paths, contents?)Each file is on the disk, with those contents
assertMissing(paths)No file is at the paths
assertCount(folder, count, recursive?)The folder has that many files
assertDirectoryEmpty(folder)The folder has no files

Interacting with time#

t.travelTo(), t.travel() and t.freezeTime() move the clock your code reads. Time travel covers them.

Privacy policy ·

esc