Akademibokhandeln
Writing tests can be daunting when starting out, it’s hard to know exactly what to test and then learning the API for your test tool.
I wanted to share some small tips that can be useful when starting out.
expect.objectContaining()
In some cases you are only interested in the value of one or just a few properties in an object. To check for a specific property you can use expect.objectContaining
to check if the object contains a property with the expected value.
In the code below we’re checking if a report dialog function has been called with the users name and email.
The actual object is much larger but we don’t really care about the other properties, in this case the user information is the moving parts in the object.
expect(showReportDialog).toHaveBeenCalledWith(
expect.objectContaining({
user: {
name,
email,
}
})
);
expect.anything()
Callback functions or randomly generated values can sometimes be a hassle to handle in tests since they might change, but it is possible to ignore specific properties or arguments using expect.anything
.
function loadScript(scriptUrl:string, callback:() => unknown) { ... }
When testing the above function we ’re not interested in the callback function and only wants to check if loadScript have been called with the correct script.
it("should call loadScript", () => {
someFunctionUsingLoadScript();
expect(loadScript).toHaveBeenCalledWith(
"script.js",
expect.anything()
);
}
expect.anything
does not match null or undefined values
expect.any()
Another way to match more broadly is expect.any(constructor)
where you can accept any match based on the constructor being passed to it.
expect(someFn).toHaveBeenCalledWith({
someNumber: expect.any(Number),
someBoolean: expect.any(Boolean),
someString: expect.any(String)
});
expect.assertions()
When doing asynchronous tests it can be helpful to make sure that all assertions have been run when the test ends.
The expect.assertions(Number)
ensures that the correct number of assertions have been made.
test('prepareState prepares a valid state', () => {
expect.assertions(1);
prepareState((state) => {
expect(validateState(state)).toBeTruthy();
})
return waitOnState();
})
test.each
For some unit tests you may want run the same test code with multiple values. A great way to do this is using the test.each
function to avoid duplicating code.
Inside a template string we define all values, separated by line breaks, we want to use in the test. The first line is used as the variable name in the test code.
test.each`
someId
${undefined}
${null}
${""}
`("$someId should reject promise", async ({ someId}) => {
expect.assertions(1);
await expect(someFn(someId))
.rejects.toEqual(errorMessage);
});
Multiple input variables can be added separated by the pipe (|) character.
test.each`
someId | anotherValue
${undefined} | ${a}
${null} | ${b}
${""} | ${c}
`("$someId with $anotherValue should reject promise", async ({ someId, anotherValue }) => {
expect.assertions(1);
await expect(someFn(someId, anotherValue))
.rejects.toEqual(errorMessage);
});
Note: it is also possible to define the values as arrays, read more in the official documentation.
jest.requireActual
This is just a reminder never to forget adding jest.requireActual
when mocking libraries. If you do forget, it can lead to weirdness that may take several hours to solve (talking from personal experience here 😁).
So what does it do?
When mocking a library you may only want to mock a specific function of the library and keep the rest of the library intact.
jest.mock("@material-ui/core", () => ({
...jest.requireActual("@material-ui/core"),
useMediaQuery: jest.fn()
}));
So in the code above we create a new mock object, using jest.requireActual
to spread all the functions of the library and only mock useMediaQuery in this case.
Stockholm Reflow
Stockholmshem
Infografik för EU-upplysningen
Stockholmskällan
Byrålådan – Fastighetsbyrån
As a part of the frontend core team for the RayCare-product I supported the other development teams in the project with the frontend framework, creating generic, reusable and well tested components.
Åhléns Checkout
My first major project at Åhlens was changing payment provider to Adyen and at the same time converting the existing checkout to a whole new experience.

The existing checkout was very cumbersome to use with several steps before even being able to complete a purchase and for every step customers were dropping off.
So much UX and design time was spent streamlining the checkout experience, trying to reduce the number of steps the customer has to complete.
In the end we went from a five-step checkout to, for logged in members, a one- step checkout using the default values.
- Revenue
- 800 m
- Monthly visits
- >3 m
- Mobile devices
- 79%
Tech stack
- TypeScript
- React
- Redux
- Material UI
- Storybook
- Jest
- Lerna
We decided to do a total rebuild, using React and Redux to build a single-page app using only REST APIs so that the checkout can be separated from the back-end in the future.
Lerna was used to split the project into different NPM-packages for reuse on other parts of the website.
All components was developed in a Storybook environment which worked well since the whole checkout is REST-based.
The added bonus with using Storybook is the we automatically get a component library and style guide of the existing components.
Services
- Adyen
- Klarna
- Swish
- Ingrid
- Intershop 7.9
The checkout relies on several external services for payment, tracking and shipping. With Adyen as our payment provider we use widgets for both Klarna and Swish. We also use a widget from Ingrid to handle shipping and where to pick up the package.
Learnings
Use a component library
Using Material UI was a great decision that not only gave us ready to use components, which made building custom components really easy, but it also gave us a base design that we could build on, saving perhaps as much as 25% development time.
Beware bad APIs
Unfortunately the REST API in Intershop 7.9 leaves a lot to be desired, with several calls having to be made for every basket update.
This is said to be better in Intershop 7.10.