Kategorier
Code testing

Tips and tricks for testing with Jest

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.

Kategorier
Projects

Å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.

Kategorier
Code Frontend

Extending the theme in Material UI with TypeScript

When we started using Material UI (version 3) the support for extending the built-in theme was pretty lacking. The theme interface did not handle any additional color settings such as ”success” or ”warn” and trying to extend the theme did not work since parts of interfaces can’t be overwritten.

So instead of extending the theme we used a separate object with corresponding interface to handle the extra colors that we needed. Not ideal but as the colors only were used in a few places we could afford to wait for the support in MUI to get better.

Flash forward a year and the support is here so extend the theme we did!
The documentation tells us to use module augmentation to merge our theme with the built-in theme by creating a index.d.ts file and adding our properties in that.

The official way of doing it

So if I want to extend the typography object to accept a secondaryFontFamily I would have to do something like this:

declare module "@material-ui/core/styles/createTypography" {
  interface TypographyOptions {
    secondaryFontFamily: string;
  }
  interface Typography {
    secondaryFontFamily: string;
  }
}

And then creating a custom theme factory function to create the theme.

import { createMuiTheme} from '@material-ui/core/styles';

export const createMyTheme():CustomTheme => createMuiTheme({
  palette: createPalette({}),
  typography: {
    secondaryFontFamily: "Georgia"
  }
});

This works well but still uses the Theme interface which makes it harder to know what has been extended.

Our project setup

We package our code in different NPM packages and use Lerna to handle the development environment.

That means that the theme is used over several packages and when we implemented the solution above we quickly realized that we had to add the index.d.ts file in every project, making it very cumbersome to add new attributes in the future.

Back to the drawing board.

A different solution

So we need an interface for our customised theme that we can share with our packages.

import React from "react";
export interface CustomTypography extends Typography {
  secondaryFontFamily: string;
}
export interface CustomTheme extends Theme {
  typography: CustomTypography;
}

export const createMyTheme():CustomTheme => createMuiTheme({
  palette: createPalette({}),
  typography: {
    secondaryFontFamily: "Georgia"
  }
});

That will unfortunately result in the following error:
Image of code with a typescript error: S2345: Argument of type '{ palette: Palette; typography: { secondaryFontFamily: string; }; }' is not assignable to parameter of type 'ThemeOptions'. Types of property 'typography' are incompatible.

TypeScript does not allow interfaces to be merged and since CustomTheme extends Theme it seemed that we are out of luck.

Then I discovered Omit.

TypeScript Omit to the rescue!

Omit<T,K> is an utility type that constructs a type by picking all properties from T and then removing K.

So by using Omit we can create our own utility type Modify. (Props to Qwerty )

type Modify<T, R> = Omit<T, keyof R> & R;

Which will merge two interfaces, removing any members on T that exists in R and then adding R to the resulting type.

So using Modify we can do this instead:

import { Theme } from "@material-ui/core";
import { Typography } from "@material-ui/core/styles/createTypography";

export type CustomTypography = Modify<
  Typography,
  {
    secondaryFontFamily: string;
  }
>;

export type CustomTheme = Modify<
  Theme,
  {
    typography: CustomTypography;
  }
>;


export const createMyTheme():CustomTheme => {
  const baseTheme = createMuiTheme({
    palette: createPalette({})
  });

  return {
    ...baseTheme,
    typography: {
      secondaryFontFamily: "Georgia"
    }
  }
});

And use it in our app like this:

const MyApp = () => {
const myTheme = createMyTheme();
  return (
    <ThemeProvider<CustomTheme> theme={myTheme}>
      <CssBaseline />
        <SomeComponent />
    </ThemeProvider>
  );
};

I hope this can help get someone with the same problem some ideas and if you have solved the problem in another way, please let me know.

Kategorier
Frontend

How to mock the Redux useSelector hook

There is an official way of using RTL with redux as some people pointed out, but for small quick tests mocking useSelector may still be of use. 🙄

Recently I finally made the switch from Enzyme to React testing library (RTL) which also means that instead of rendering components using shallow like Enzyme proposes, with React testing library the whole component and its child components is rendered, much like Enzymes mount.

The switch to RTL coupled with using hooks instead of HOCs when using Redux got me writing a lot of new component tests but I did run in to some problem when I tried to use the useSelector-hook from Redux multiple times expecting different responses.

The component that I wanted to test as a search component that made calls similar to this:

const MySearchComponent = () => {
  const { query, rows } = useSelector((state) => 
    state.config);

  const {
      items,
      hasMore
    } = useSelector((state) => state.search);

  return (...)
}

useSelector takes a callback function that takes the state as an argument and returns a slice of the state.

So my first approach when trying to test the component was to send two different responses.

jest.mock("react-redux", () => ({
  useSelector: jest.fn()
    .mockReturnValueOnce(mockConfigState)
    .mockReturnValueOnce(mockSearchState)
}));

describe("MySearchComponent", () => {
  afterEach(() => {
    useSelector.mockClear();
  });
  it("should render", () => {
    const { getByTestId } = render(<MySearchComponent />);
    expect(...)
  });
});

Which worked fine until I realised that a child component also calls useSelector and therefore crashed. 😱

I knew I needed something that would support all possible selectors that I needed but still could be modified on a test by test basis.
I had a mock state ready, but not the method to alter and inject it.
Until I ran across jest.fn().mockImplementation

The solution to my problems

useSelector takes a callback as its argument and all I had to do was to call that callback with a compatible state that would satisfy all my components needs and they would do the rest as implemented.

jest.mock("react-redux", () => ({
  useSelector: jest.fn()
}));

describe("MySearchComponent", () => {
  beforeEach(() => {
    useSelector.mockImplementation(callback => {
      return callback(mockAppState);
    });
  });
  afterEach(() => {
    useSelector.mockClear();
  });
  it("should render a query", () => {
    const { getByTestId } = render(<MySearchComponent />);
    expect(getByTestId("query_testId").textContent)
      .toEqual(mockAppState.config.query)
  });
  it("should not render if query is empty", () => {
      const localMockState = {
        ...mockAppState,
        config: {
          ...mockShoppingState.config,
          query: ""
        }
      };
      useSelector.mockImplementation(callback => {
        return callback(localState);
      });
    const { queryByTestId } = render(<MySearchComponent />);
    expect(queryByTestId("query_testId")).toBeNull();
  });
});

So in the code above I mock useSelector from the react-redux npm package and replaces it with a function that executes any given callback function with my mocked state as an argument. This is done before every test.

In the second test I create a second mocked state that I want to use for just that test so I override useSelector to make sure it uses my updated state instead of the default mock state.

Parting words

I hope this helped someone to learn a little bit more about how to test their code and what can be achieved with jest and tools like RTL (which is great, try it!)

All typos my own and please leave a comment if you have a question or something does not make sense.

Kategorier
Frontend

How to add an RSS-feed to your Next.js site

I love RSS-feeds (and still curse Google for cancelling Google Reader) and use them as my main news source for things I find interesting so with this article I would like to help people to add RSS-feeds to their blogs.

If you read my article about how to add a sitemap.xml to your next.js site you will recognise much of the code, it’s pretty much the same base but with slightly different XML-markup.

Creating the page

First off we need a page that can return the XML. I suggest you name it ”rss”, ”feed” or something similar.

In getInitialProps we get our blog posts and set the ”Content-Type”-header to ”text/xml” so the browser knows that should expect XML instead of HTML.
We then generate the XML and passes it on to the browser using res.write.

export default class Rss extends React.Component {
  static async getInitialProps({ res }: NextPageContext) {
    if (!res) {
      return;
    }
    const blogPosts = getRssBlogPosts();
    res.setHeader("Content-Type", "text/xml");
    res.write(getRssXml(blogPosts));
    res.end();
  }
}

Generating the base XML for the feed

For the base XML document you will need to add some basic information like the title of the log, a short description, link to your website and the language of the content.
Title, link and description are mandatory elements in the RSS specification but add as many optional elements as you find useful.

const getRssXml = (blogPosts: IBlogPost[]) => {
  const { rssItemsXml, latestPostDate } = blogPostsRssXml(blogPosts);
  return `<?xml version="1.0" ?>
  <rss version="2.0">
    <channel>
        <title>Blog by Fredrik Bergqvist</title>
        <link>https://www.bergqvist.it</link>
        <description>${shortSiteDescription}</description>
        <language>en</language>
        <lastBuildDate>${latestPostDate}</lastBuildDate>
        ${rssItemsXml}
    </channel>
  </rss>`;
};

Adding XML for the blog items

With the basic stuff out of the way all we need to go is generate some XML for the blog posts and figure out when the blog was updated.

The item element should at least contain a link to the blog post, the date when it was published and the text. You can either opt to put a short description and force the user to come visit your page or put the whole post in the XML.

If you have HTML-markup in your text you need to enclose it within a <![CDATA[${post.text}]]>-tag or HTML encode the text.

const blogPostsRssXml = (blogPosts: IBlogPost[]) => {
  let latestPostDate: string = "";
  let rssItemsXml = "";
  blogPosts.forEach(post => {
    const postDate = Date.parse(post.createdAt);
    if (!latestPostDate || postDate > Date.parse(latestPostDate)) {
      latestPostDate = post.createdAt;
    }
    rssItemsXml += `
      <item>
        <title>${post.title}</title>
        <link>
          ${post.href}
        </link>

        <pubDate>${post.createdAt}</pubDate>
        <description>
        <![CDATA[${post.text}]] >
        </description>
    </item>`;
  });
  return {
    rssItemsXml,
    latestPostDate
  };
};

Finishing up

The last thing you need to do is to add a link to your feed somewhere in the head of your document.

<link 
  rel="alternate" 
  type="application/rss+xml" 
  title="RSS for blog posts" 
  href="https://www.bergqvist.it/rss" />

This will make it easier for browsers and plugins to auto discover your feed.

The code is available as a gist on github and please leave a comment with any feedback.

Kategorier
Frontend

Adding a dynamic sitemap.xml to a next.js site

While building my blog with Next.js I naturally wanted to support sitemaps because it can supposedly help out the search engines. For my small blog it will not make any difference but for larger sites it’s more important. Google has this to say.

Using a sitemap doesn’t guarantee that all the items in your sitemap will be crawled and indexed, as Google processes rely on complex algorithms to schedule crawling. However, in most cases, your site will benefit from having a sitemap, and you’ll never be penalized for having one.

Page for the sitemap

The first thing we need to do is to create a sitemap.xml.ts page in the ”page”-folder. This will expose a https://yourdomain.com/sitemap.xml url that you can submit to search engines. if you want to you can omit the .xml part and only use /sitemap, Google search console will accept the url anyway.

We want to make sure that we set the Content-Type header to text/xml and to write our xml output to the response stream.

export default class Sitemap extends React.Component {
  static async getInitialProps({ res }: any) {
    const blogPosts = getBlogPosts();
    res.setHeader("Content-Type", "text/xml");
    res.write(sitemapXml(blogPosts));
    res.end();
  }
}

Generating the base xml

For the site map we want to list all pages on the site, apart from the blog posts we have to add all additional pages that we want the search engines to find.

I have an about page that I add manually together with the index page but if you have many pages I suggest you create an array and add them in a more automated way.

I won’t go into the inner workings of all the properties of a sitemap but I want to mention the <priority>-tag that lets you set a value between 0 and 1 indicating how important you think the page is.
<lastmod> is used to indicate when the page was changed.

const sitemapXml = (blogPosts: IBlogPostListItem[]) => {
  const { postsXml, latestPost } = blogPostsXml(blogPosts);
  return `
  <?xml version="1.0" encoding="UTF-8"?>
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <url>
      <loc>https://www.bergqvist.it/</loc>
      <lastmod>${latestPost}</lastmod>
      <priority>1.00</priority>
    </url>
    <url>
      <loc>https://www.bergqvist.it/about</loc>
      <priority>0.5</priority>
    </url>
    ${blogPostsXml}
  </urlset>`;
};

Adding xml for the blog posts

As mentioned above I want to add the dynamic blog post pages to the site map as well. In the blogPostsXml-function I generate xml for all posts and keep track of when the latest post was posted.

const blogPostsXml = (blogPosts: IBlogPostListItem[]) => {
  let latestPost = 0;
  let postsXml = "";
  blogPosts.map(post => {
    const postDate = Date.parse(post.createdAt);
    if (!latestPost || postDate > latestPost) {
      latestPost = postDate;
    }
    postsXml += `
    <url>
      <loc>${post.url}</loc>
      <lastmod>${postDate}</lastmod>
      <priority>0.80</priority>
    </url>`;
  });
  return {
    postsXml,
    latestPost
  };
};

Last words

Make sure to check that you do not add any pages to the sitemap that is blocked in your robots.txt.

If you have a large site with many pages (100 or more) you can split up the sitemap into several smaller ones, for very large sites this is a must!

I hope this could help out someone and please leave a comment. A full gist of the code can be found here

Kategorier
Frontend

Typing React with typescript

TypeScript has really taken off lately in the React world and rightly so, it’s a great way to keep your code documented and help you keep some errors at bay.

I have worked with TypeScript in a few projects the last few years, all involving Angular in some form but never with React so I had some new challenges in front of me when I started my current project with React and TypeScript.

Functional components

Let us start off easy with a basic functional component:

interface OwnProps {
  myProp:string;
}

const MyButton: React.FC<OwnProps> = (props) => {
  return (<button />);
}

export default MyButton as React.ComponentType<OwnProps>;

We create an interface called OwnProps where we define all props we want the component to have. 

OwnProps is then used to define the component: React.FC<OwnProps> as well as when we export the component as React.ComponentType<OwnProps> to clearly signal what props are available.

In this basic example it may seem unnecessary but, as we shall see further down, when components get more complex it will save us some headache.
This approach will also help negate the following error:

error TS2604: JSX element type ’MyComponent’ does not have any construct or call signatures.

Wrapping another component

In some cases you might want to wrap another component and include that components interface in your own. This is usually the case when working with base components from a library and since we use Material UI (MUI for short)as a base component library I will use that in the example.

interface OwnProps {
  buttonText:string;
}
type Props = OwnProps & ButtonProps;

const MyButton: React.FC<Props> = ({buttonText, ...rest}) => {
  return (<Button {...rest}>{buttonText}</Button>);
};

export default MyButton as React.ComponentType<Props>;

The Props type can be seen as the sum of all parts that the component will consist of. In this case we want to use ButtonProps from MUIs Button component and merge it with our own and expose both props to the consumers of the component. 

This is still not very advanced but since we use MUI we also use JSS for styling so let us add that to the mix!

Using WithStyles and WithTheme

Since we use MUI we handle styling with JSS and the generated CSS classes are injected via the withStyles HOC. This caused some issues since a classes object containing the class names is injected into your props and to use classes you would need to include that object in your prop type.

Luckily we have the WithStyles type to help us! 
WithStyles<typeof styles> takes a generic type argument of your style object so you don’t have to worry about it keeping your types DRY.

The typescript section at Material UI explains the problems with withStyles more in detail, ready up on it if you plan to use MUI and TypeScript.

const styles: (theme: Theme) => StyleRules<string> = theme =>
  createStyles({
    root: {
      margin: theme.spacing.unit
    }
  });
interface OwnProps {
  buttonText:string;
}
type PublicProps = OwnProps & ButtonProps;
type Props = PublicProps & WithStyles<typeof styles>;
const MyButton: React.FC<Props> = ({classes, buttonText, ...rest}) => {
  return (
    <Button {...rest} className={classes.root}>
      {buttonText}
    </Button>);
};
export default withStyles(styles)(MyButton) as React.ComponentType<PublicProps>;

The addition we’ve done here is adding a PublicProps type and using that instead of the Props type when exporting the component. This is of course because we also want to use WithStyles but not expose it to anyone using the button.

Had we used the Props type instead of PublicProps we would get a pesky TypeScript error complaining about classes property missing.

TS2741: Property 'classes' is missing in type '{buttonText: string}' but required in type '{ classes: Record<string, string>}'

Redux connect and compose

But what would React be without state handling? We use Redux for this so let us connect MyButton and get the buttonText prop from state instead.

const styles: (theme: Theme) => StyleRules<string> = theme =>
  createStyles({
    root: {
      margin: theme.spacing.unit
    }
  });

interface StateProps {
  buttonText:string
}
interface DispatchProps {
  dispatch: ThunkDispatch<IAppState, undefined, Action>;
}
interface OwnProps {}
type PublicProps = OwnProps & ButtonProps;
type Props = PublicProps & 
             DispatchProps & 
             StateProps & 
             WithTheme &
             WithStyles<typeof styles>;

const MyButton: React.FC<Props> = ({classes, buttonText, ...rest}) => {
  return (
    <Button {...rest} className={classes.root}>
      {buttonText}
    </Button>);
};

const mapStateToProps = (state: IAppState): StateProps => {
  return {
    buttonText: state.buttonText
  };
};

export default compose(
  withStyles(styles, { withTheme: true }),
  connect<StateProps, DispatchProps, OwnProps, IAppState>(mapStateToProps)
)(MyButton) as React.ComponentType<PublicProps>;

We have not started using hooks for our state so we go with the good old connect. Since we now use both connect and withStyles we need to use compose to merge them.

We create StateProps as return type of mapStateToProps and DispatchProps which types the dispatch function that is returned by default if we don’t add a mapDispatchToProps function. In out case we use Thunk so if you use some other tool you need to use that instead.

I have also added an example of using the MUI theme in the component, this is done by adding { withTheme: true } as a second argument to withStyles
This will inject a theme property into your props so we need to specify that in our Props type using WithTheme from MUI.

Bonus: typing the useState hook

Not really rocket science but good to know!

const [state, setState] = useState<string>("Hello world!")`

End note

TypeScript can be very frustrating when starting out with many errors that are not clear so I hope this article could be of some help or inspiration, it took us a while to settle on a TypeScript pattern that worked for us and helped us mitigate most of the errors that might occur when working with different libraries.

Feel free to leave any suggestions in the comment field.

A gist of the code examples are available at GitHub.