Jordan Mocks Puppeteer with Jest
Tags: unittests, javascript, puppeteer, typescript
Published: 2020-04-20 22:30:23

---
title: Jordan Mocks Puppeteer with Jest
published: true
date: 2020-04-20 13:30:00 UTC
tags: Unit Tests,Javascript,Puppeteer,Typescript
canonical_url: https://javascriptwebscrapingguy.com/jordan-mocks-puppeteer-with-jest/?utm_source=rss&utm_medium=rss&utm_campaign=jordan-mocks-puppeteer-with-jest
---

[Demo code here](https://github.com/aarmora/jordan-tests-puppeteer-scripts)

Go ahead right now and google “Unit testing Puppeteer scripts”. Do it. The results…are good. If you are trying to use Puppeteer to test your product.

![Google results for unit testing puppeteer scripts](https://javascriptwebscrapingguy.com/wp-content/uploads/2020/04/image-4.png)

But what if your product _is_ a Puppeteer script? I have searched long and hard and haven’t been able to find a good solution. And this is a big problem for someone like me who loves to have good unit tests and loves to use Puppeteer.

So…the purpose of this post is to show how I unit test Puppeteer scripts using [Jest](https://github.com/facebook/jest). The test framework isn’t overly important but this post makes a lot more sense for those of you who are usign Jest for your unit tests. If you aren’t familiar with [Puppeteer](https://github.com/puppeteer/puppeteer), I’d recommend my guide for getting starting with [web scraping using Puppeteer](https://javascriptwebscrapingguy.com/puppeteer-jordan-teaches-web-scraping/). Of course, I don’t imagine many of you will be reading this post if you don’t use Puppeteer.

## Getting started

![Ross from friends mocking gif](https://media.giphy.com/media/f6zcJssQkdT4VQ7dIF/giphy.gif)<figcaption>Ross. Mocking.</figcaption>

I created a simple function that I could test. While this isn’t as big or complex as a lot of the things Puppeteer is used for, it does showcase most of the key functionalities and goes pretty deep into the Puppeteer module.

```
export async function action() {
	const browser = await puppeteer.launch({ headless: false });
	const page = await browser.newPage();

	const url = 'https://javascriptwebscrapingguy.com';

	await page.goto(url)

	const entryTitlesHandles = await page.$$('h2.entry-title');

	const links: any[] = [];

	for (let i = 0; i < entryTitlesHandles.length; i++) {
		const link = await entryTitlesHandles[i].$eval('a', element => element.getAttribute('href'));

		links.push(link);
	}

	await browser.close();

	return links;

}
```

I navigate to [javascriptwebscrapingguy](https://javascriptwebscrapingguy.com/), get all blog posts, and then pluck the href out of the element of each one. This way I have to mock `puppeteer.launch`, `browser.newPage`, `page.goto`, `page.$$`, `elementHandle.$eval` (though `$eval` also exists on the page method), and `browser.close`.

I’ve never mocked anything that deep before. `puppeteer.launch` returns a `Browser`, which has a method that returns a `Page`, which has a method that returns either an `ElementHandle` (or an array of them).

## The mock

![Ferris Bueller mocking gif](https://media.giphy.com/media/pQQRV5MHXPpqo/giphy.gif)<figcaption>Get it? He’s mocking.</figcaption>

Here’s the [mock](https://github.com/aarmora/jordan-tests-puppeteer-scripts/blob/master/test/mockPuppeteer.ts) itself:

```
import { Browser, Page, ElementHandle } from "puppeteer";

export const stubPuppeteer = {
	launch() {
		return Promise.resolve(stubBrowser);
	}
} as unknown as any;

export const stubBrowser = {
	newPage() {
		return Promise.resolve(stubPage);
	},
	close() {
		return Promise.resolve();
	}
} as unknown as Browser;

export const stubPage = {
	goto(url: string) {
		return Promise.resolve();
	},
	$$(selector: string): Promise<ElementHandle[]> {
		return Promise.resolve([]);
	},
	$(selector: string) {
		return Promise.resolve(stubElementHandle);
	},
	$eval(selector: string, pageFunction: any) {
		return Promise.resolve();
	}
} as unknown as Page;

export const stubElementHandle = {
	$eval() {
		return Promise.resolve();
	}
} as unknown as ElementHandle;
```

This goes through all of the things I use in the test and fully mocks them out. You can see that from top to bottom, it provides the stubbed methods which include the stubbed methods that that stubbed method provides. Me writing it makes it sound terribly confusing. Hopefully seeing it above is more helpful.

## The tests

![Ross mocking gif](https://media.giphy.com/media/7noLIFRgv68us/giphy.gif)

To start, this was the part that was most difficult for me to understand or get right. Jest is pretty great for testing and can allow you to just automock modules by just going `jest.mock('moduleName')`.

That’s pretty powerful but for me, unless there is some voodoo I don’t know about, it wouldn’t handle deep modules such as Puppeteer. This makes sense, because how could it know what you want the deeper methods to return or not return. You are able to provide your mock for the module, however, like this:

```
jest.mock('puppeteer', () => ({
	launch() {
		return stubBrowser;
	}
}));
```

And…this provides the rest. I really tried to just return `stubPuppeteer` directly but I could not figure out why it wouldn’t work. I may mess around it with more for next week’s post. It just throws the following error any time I try:

![](https://javascriptwebscrapingguy.com/wp-content/uploads/2020/04/image-5.png)

In any case, doing it this way, returning the manual mock for puppeteer, it provides all the methods needed. All of the tests are shown in the [demo code](https://github.com/aarmora/jordan-tests-puppeteer-scripts/blob/master/test/action.test.ts) but I would like to discuss some of the more tricky ones here.

This section of code was the most complicated in my opinion:

```
	const entryTitlesHandles = await page.$$('h2.entry-title');

	const links: any[] = [];

	for (let i = 0; i < entryTitlesHandles.length; i++) {
		const link = await entryTitlesHandles[i].$eval('a', element => element.getAttribute('href'));

		links.push(link);
	}
```

I get the `ElementHandle`s and then I loop through them, calling `$eval` and getting the href attribute. So I tested it with just a single link and then with two.

```
	test('that it should return an array with a single link', async () => {
		jest.spyOn(stubPage, '$$').mockReturnValue(Promise.resolve([stubElementHandle]));
		jest.spyOn(stubElementHandle, '$eval').mockReturnValue(Promise.resolve('https://pizza.com'));

		const result = await action();

		expect(result).toEqual(['https://pizza.com']);
	});

	test('that it should return an array with multiple links', async () => {
		jest.spyOn(stubPage, '$$').mockReturnValue(Promise.resolve([stubElementHandle, stubElementHandle]));
		const stubElementHandleSpy = jest.spyOn(stubElementHandle, '$eval')
			.mockReturnValueOnce(Promise.resolve('https://pizza.com'))
			.mockReturnValueOnce(Promise.resolve('https://github.com'));

		const result = await action();

		expect(result).toEqual(['https://pizza.com', 'https://github.com']);
		expect(stubElementHandleSpy).toHaveBeenCalledTimes(2);
	});
```

Using Jest’s `spyOn` and `mockReturnValue`, I was able to easily return the value I wanted to for each of those functions. When I wanted to handle an array, I just used `mockReturnValueOnce` and then daisy chained them where they would return one value the first time the function was called and then second value the second time it was called.

Honestly, it all worked really great and was simple. The mock was trickiest part. After that, it was unit testing as usual. I had a good time.

The end.

[Demo code here](https://github.com/aarmora/jordan-tests-puppeteer-scripts)

## Looking for business leads?

Using the techniques talked about here at [javascriptwebscrapingguy.com](https://javascriptwebscrapingguy.com), we’ve been able to launch a way to access awesome business leads. Learn more at [Cobalt Intelligence](https://cobaltintelligence.com)!

The post [Jordan Mocks Puppeteer with Jest](https://javascriptwebscrapingguy.com/jordan-mocks-puppeteer-with-jest/) appeared first on [JavaScript Web Scraping Guy](https://javascriptwebscrapingguy.com).