Mock implementation
Loading "Mock Implementation (๐ solution)"
Run locally for transcripts
Let's start by going to the first test case for our
OrderController
and spying on the isItemInStock
method of the created controller
instance:test('creates an order when all items are in stock', () => {
const controller = new OrderController()
vi.spyOn(controller, 'isItemInStock').mockReturnValue(true)
controller
) is scoped to this particular test, there's no need to introduce any test setup to reset the mock.By using
.mockReturnValue()
, I am forcing the .isItemInStock()
method to always return true when run in this test. With that behavior fixed, I can write the assertions around how the new order should be created:test('creates an order when all items are in stock', () => {
const controller = new OrderController()
vi.spyOn(controller, 'isItemInStock').mockReturnValue(true)
const cart: Cart = [
{
id: 4,
name: 'Porcelain vase',
quantity: 1,
},
]
const order = controller.createOrder({ cart })
expect(order).toEqual<Order>({
cart: [
{
id: 4,
name: 'Porcelain vase',
quantity: 1,
},
],
})
})
I prefer keeping my assertions explicit so I repeat the entirecart
object within myexpect()
statement.
In the next test case, I'd like to do things a little differently. I will still spy on the
.isItemInStock()
method, but instead of mocking its return value, I will mock that method's implementation.test('throws an error when one of the items is out of stock', () => {
const controller = new OrderController()
vi.spyOn(controller, 'isItemInStock').mockImplementation((item) => {
return item.id === 4
})
By mocking the implementation, I am creating a mock with conditional behavior. It will behave differently based on the cart
item
that's being checked. Only the item with id
that equals 4
will be considered in stock. This way, I am able to reproduce the error throwing logic as well as assert on the controller
class including the right item IDs in the error message.test('throws an error when one of the items is out of stock', () => {
const controller = new OrderController()
vi.spyOn(controller, 'isItemInStock').mockImplementation((item) => {
return item.id === 4
})
const cart: Cart = [
{
id: 4,
name: 'Porcelain vase',
quantity: 1,
},
{
id: 5,
name: 'Sofa',
quantity: 3,
},
{
id: 6,
name: 'Microwave',
quantity: 1,
},
]
expect(() => controller.createOrder({ cart })).toThrowError(
'Failed to create an order: found out of stock items (5, 6)',
)
})
Alternative approach
Because we are mocking the
.isItemInStock()
method entirely in this test, we focus our the logic of the .createOrder()
method of the OrderController
class. That is where we are establishing the test boundary.Alternatively, we could also mock the dependency on
stock.json
to decide which items are in stock on the data level. Notice how that would've yielded a different test that would also include the functionality of the .isItemInStock()
method (the filtering). You will learn more about module mocking later in this workshop.