Real-world performance of the Apple M1 in software development

There are enough videos on YouTube out there to show how awesome the new Macs are, but I want to share my perspective as a software developer.

About six weeks ago, I was too hyped not to buy an ARM-based Mac, so I ordered a basic MacBook Air with 8 GB RAM (16 GB was hard to get at this time). As strange as it sounds, I don’t regret buying only 8 GB of RAM. On an Intel-based Mac this would be an absolute pain in the ass, even my old 15” MacBook Pro Late 2017 with 16 GB struggles sometimes with RAM usage.

It’s really amazing how good this small, passively cooled MacBook Air is keeping up. In many scenarios it even surpasses my MacBook Pro with ease. I never had an Intel-based MacBook Air, but the last time is used a dual-core CPU for development, was not pretty und that was a pretty decent i5 and not a ultra-low voltage i3.

Speed, Speed, Speed

Unfortunately I couldn’t really develop software on the MacBook Air for a while, since Java and IntelliJ were not available for aarch64-based Macs. Of course I tried Rosetta 2, but at least for these two, it’s quite slow. NodeJS on the other hand is incredibly fast.

All this changed after my christmas vacation. IntelliJ was updated and thankfully Azul released a JDK 8 for ARM-Macs. A native version of Visual Studio Code is also available and quite fast.

So, no more introductions, here are some real-world scenarios and numbers.

I currently work on a Java project with a steadily growing codebase of Kotlin. It’s a little special, since another part of the application is written in Ruby. We’re using JRuby, so it’s bundled all together with a Vue-based frontend in a WAR-File with Maven.

Maven Build Times

All build times are from fully cached dependencies, so there are no interferences from my internet connection.

Used devices:

  • 15” MacBook Pro Late 2017: Intel Core-i7 7700HQ, 16 GB RAM
  • 13” MacBook Air Late 2020: Apple M1, 8 GB RAM
  • PC: AMD Ryzen 9 3950X, 32 GB RAM
Device Build time with tests Build time without tests
MacBook Pro 223 s 183 s
MacBook Air 85 s 63 s
PC 84 s 66 s

Well, a small, passively cooled MacBook Air is as fast as a full-blown and custom water-cooled 16-core monster of a PC. The MacBook Pro gets utterly destroyed. To get this straight: the cheapest notebook Apple makes, destroys a MacBook Pro that costs more than twice as much.

Ruby Unit Test Times

The test suite contains 1,087 examples. Please keep in mind, that I had to use Rosetta 2 in order to get everything running on the MacBook Air, since not all used Ruby Gems are compatible with ARM at this time. All tests were run with Ruby 2.7.1.

Device Test duration
MacBook Pro native 1.9 s
MacBook Air with Rosetta 2 1.1 s
PC native 1.3 s

Yeah, it’s quite fast, compared to a Suite of Java-based unit tests, but even here the MacBook Pro has no chance at all.

Frontend Build Times

The frontend is a Vue-based single-page application. As with Ruby, I had to use NodeJS with Rosetta 2, since not all used modules are compatible with ARM.

Device Test duration
MacBook Pro native 27.8 s
MacBook Air with Rosetta 2 20.7 s
PC native 20.6 s

Well, it’s more than obvious now, that the MacBook Pro has no chance at all against my MacBook Air. It’s not just the performance. After a few seconds of load, the MacBook Pro sounds like my F/A-18C in DCS immediately before a carrier launch, while the MacBook Air has no fan and therefore makes no noise at all.

And the battery life. Oh my god. Ten straight hours of development with IntelliJ and Visual Studio Code is entirely possible now, all while staying cool and quiet.

Even the dreaded battery murderer Google Meet is no problem anymore. My MacBook Pro on battery would last perhaps 2.5h max. The MacBook Air is capable of 8, perhaps even 9 hours of Meet. It’s as insane as an 8h long Meet itself.

Ah, yes, there is another thing: Meet does not cripple the performance anymore. The MacBook Air is totally usable with a Meet going on, while my MacBook Pro becomes sluggish as hell and is barely usable (even without Chrome and frakking Google Keystone).


I will make it short: if your tools and languages are already supported or at least quite usable with Rosetta, go for it. I would recommend 16 GB or more (depending on future models), if you want to buy one. I’m surprised that a 8 GB MacBook Air is that capable and to be honest, I don’t feel like there a going to be a problem for a while, but no one regrets more RAM …

Air vs Pro

The 13” MacBook Pro is little faster over longer periods of load due to active cooling, it has more GPU-cores, the love or hated Touch Bar and a bigger battery. If you need this, go for it, but if you can wait, I’d recommend to wait for the new 14” and 16” Pro models.

They will be real power houses with 8 instead of 4 Firestorm cores, vastly more RAM and even bigger batteries. And hey, perhaps they come with MagSafe and some other ports we MacBook users didn’t see for a while.

Ryzen vs Apple Silicon and why Zen 3 is not so bad as you may think

Apple‘s M1 is a very impressive piece of hardware. If you look at the benchmarks, like SPECperf or Cinebench, it‘s an absolute beast with a ridiculously low power consumption compared with a Ryzen 9 5950X.

Zen 3 is that inefficient?

10 W vs 50 W looks really bad for AMD, but the answer to this question is a little more complicated than this raw comparison of power usage.

Here are basic things to know:

  • Apple‘s M1 has four high power cores (Firestorm) and four high efficiency cores (Icestorm), this is called BIG.little architecture.
  • AMD’s Ryzen 9 5950X is a traditional x86 CPU with 16 cores and 32 Threads

So what? The benchmarks are about single core performance and Ryzen needs 40 W more. What a waste.

Right, but this says nothing about the efficiency of a single core.

I don‘t have a 5950X, but it‘s older brother, the 3950X. Same core count and about the same power usage. While monitoring with HWINFO64 on a single core run of Cinebench R20 the used core needs about 10 - 12 W.

Okay, but what happened to the other 38 watts?

As you may know AMD introduced a so called chiplet architecture with Ryzen 3000. A Ryzen 9 5950X has three dies. Two with 8 cores each and a separate chip that handles I/O like PCIe 4, DDR4 memory and the Infinity Fabric links to the CPU dies.

This I/O die consumes 15 to 17 W. It‘s a quite large chip produced in a 12 nm node by GlobalFoundries. That alone is a big reason for higher power usage compared to a modern node like TSMC‘s N7P (7 nm) used for the other two chiplets.

Why 12 nm? It‘s a compromise, since TSMCs 7 nm production capabilities are still somewhat limited and AMD takes a fairly large share of the capacity. All Zen 2 and 3 cores are produced in 7 nm, as are all modern AMD GPUs and of course there are other customers as well.

I hope AMD will improve the I/O die with Zen 4 (Ryzen 6000) dramatically. A modern process node and a bunch of better energy saving functions would do the trick.

There are still 21 W unaccounted for!

15 more cores are also unaccounted for. 21 divided by 15: about 1.4 W per core average. Seems a bit high, but possible. It depends on background tasks from the OS, the current Windows power plan etc.

To be fair, AMDs energy saving functions per core could a little bit better. They are not bad but there‘s room for improvements.

Process nodes

For a fair comparison we also have to include the process nodes. As mentioned above, Ryzen cores are manufactured in TSMC‘s 7 nm node. Apple is one step ahead in this category. The M1 and it‘s little brother A14 are already on 5 nm, again from TSMC.

This alone could account for up to 30% less power usage. There are no exact numbers available, but 20 to 30% would reasonable.

Core architecture

Current Zen 3 cores and Apple‘s Firestorm cores have fundamental differences in architecture. Modern x86 cores are small but clock quite high. On a single core load like Cinebench R23 you will get about 5 GHz from 5950X while a Firestorm core will only clock to 3.2 GHz.

Wait, what? Firestorm with 3.2 GHz is about as fast as Zen 3 with 5 GHz? That can‘t be true. Yet it is. According to the benchmarks a Firestorm core can execute about twice the instructions per cycle (IPC) as a Zen 3 core.

Firestorm is an extremely wide core design with many processing units. This is complemented by absurdly large L1 caches and re-order buffers. Much larger than on any x86 core ever.

This allows Apple to lower the clock speed significantly without compromising performance. Lower clock speeds also mean lower voltage and power usage.

But there‘s more: since the A13 (iPhone 11) Apple has quite many incremental power saving functions within every part their SoCs. They can lower the power usage to an absolute minimum or even turn parts of the silicon on and off quite fast, to be ready when the user needs more computational power and as fast they turn it off.

Modern x86 CPUs have similar functions but not quite as advanced or in as many parts as current Apple SoCs. Remember AMD‘s I/O which has basically the same power usage all the time.

Instruction set

x86 is old and can be quite cumbersome to use. Apple’s CPUs are based on ARM, a more modern instruction set. The same tasks can be achieved with fewer commands. This means faster computation and less power usage.

Under the hood current x86 CPUs have noting to do with their older cousins, but the instruction set is still the same. To be fair, over the years AMD and Intel added way more modern instructions like AVX for certain operations, but the core is still 40 years old.


Taken all this into account, a Zen 3 is not that bad compared to a Firestorm core. Of course, Apple has advantages over x86, but they are more about the process node, different architecture or the instruction set, than about the efficiency of the cores itself.

Well, and then there‘s Intel. The quite slowly awakening giant is not without hope, but they are way behind Apple and AMD. As long as their high performance CPUs are stuck on 14 nm, they are … to put it simply, fucked.

10 nm is on it‘s way, but there a still problems. The next release of desktop- and server-class CPUs will still be on 14 nm, but with a more efficient architecture backported from 10 nm CPUs. It will help, but they need a better process node and they need it asap.

10 nm could be ready for the big guys in 2021. At least new Xeons in 10 nm were announced. But the question is: how good is the node? Some of the already released 10 nm notebook CPUs are quite good, so Intel could be back in year or two.

They also plan to release Alder Lake in 2021, a BIG.little CPU design. That would be much appreciated on notebooks, but software has to be ready. Without a decent support of the operating systems, such a CPU will not work properly.

There‘s another problem: Intel‘s 7 nm node. According to YouTubers like AdoredTV or Moore‘s law is dead, 7 nm could be the 10 nm disaster all over again. Let‘s hope not.

Three competing CPU vendors on par would be amazing. Not just for pricing, but also computational power. Look what AMD did in the past three years. If someone said in 2016 that AMD would kick Intel‘s and nVidia‘s ass in four years, he would have been called a mad man. The same with Apple.

2020 is a shitty year, but the hardware? Awesome would be an understatement.

webcodr goes Netlify CMS

Until today posts on webcodr were published via a simple git-based workflow. If I wanted to create a new posts, I had to open the repository in Visual Studio Code and created a markdown file. After pushing the commit with the new file, a GitHub hook notified Netlify to pull the repo and build and publish the site.

It was quite simple and effective, but lacks comfort and does not work on iOS/iPadOS devices. After buying a new iPad Air and Magic Keyboard, I wanted a pragmatic way to write posts without my MacBook or PC.

I always wanted to try Netlify CMS, so this was my chance. The transition was really simple. I followed the guide for Hugo-based sites and adjusted the config to work with Hugo. That‘s it. Netlify CMS works just fine with the already existing markdown files. Even custom frontmatter fields within the markdown files are no problem, just set them up in the config file.

A little more configuration in the Netlify admin panel is necessary, but the guide explains everything very well. It was just a matter of 15 minutes to get everything going.

If you use a static site generator and want to have a little more comfort, Netlify CMS makes it really simple. They provide guides for every major player like Gatsby, Jekyll or Nuxt and of course Hugo. You don‘t even have to use GitHub. GitLab and BitBucket are supported as well. As are more complex workflows for more than one editor, custom authentication with OAuth or custom media libraries.


Netlify CMS supports markdown and has a basic, but decent editor with rich-text mode. But I wanted a little bit more, so I decided to write my posts in Ulysses — a specialized writing app with support of GitHub-flavored markdown, including syntax highlighting preview . It‘s available for macOS and iOS/iPadOS. All files and settings are synched via iCloud. So, once you have setup everything, you‘re good to go on any of your Apple devices.

Since Ulysses requires a subscription, I will use this to „force“ me writing more posts. 😁

I wrote this post entirely on my iPad Air and so far, I‘m quite happy with the new workflow. Of course I will not write every post this way. New posts with code examples will be easier to handle on a Mac or PC. (Hey Apple, how about IntelliJ on an iPad?)

btw: even as an enthusiast of mechanical keyboards I have to say, it‘s quite nice to type on a Magic Keyboard. I just have to get used to the smaller size. The Magic Keyboard for the 10.9“ iPad Air or 11“ iPad Pro is a compromise in size and some keys like tab, shift, backspace, enter and the umlaut keys (bracket keys on english keyboard layouts) are way smaller compared to normal-sized keyboards. It‘s bigger brother for the 13“ iPad Pro has a normal layout, but that monster of an iPad seems a bit excessive for my use case.

Kotest and JUnit with IntelliJ or: don’t frak up your toolchain upgrades

My team and I recently decided to use Kotlin for new features in our existing project. It was a great choice to implement a new authentication process and we’re now rewriting some older parts of the application from Java to Kotlin.

Actually I wanted to use Kotlin for a while now, but there were only minor tasks within the Java part of the project. That finally changed and we can focus to improve the Java backend drastiscally.

Part of this process was a library update. We decided to upgrade JUnit from 4 to 5. A big pain in the ass. I don’t think, I would do it again. JUnit 5 was also part of a bigger problem, even if it was actually PEBCAC.

Kotest and MockK features

If you already know about Kotest or want to know more about the problem I had, just skip to next headline. The Kotest introduction is a little bit longer.

As I dived more and more into Kotlin, I stumbled over a Kotest. A really neat testing framework for Kotlin. There’s nothing wrong with JUnit, but Kotest gives you way more awesome ways to structure your tests.

A little example:

package io.webcodr.demo

import io.webcodr.demo
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.mockk.*

class UserServiceTest : FunSpec() {
    private val userRepository = mockk<UserRepository>()
    private val service = UserService(userRepository)
    private lateint var user: User

    init {
        beforeTest {
            user = User(1, "Jane", "Doe", "jane@doe.com")

        afterTest {

        context("getUser()") {
            fun verifyRepoCalls() {
                verify {


            test("should succeed") {
                every {
                } returns user



            test("should fail") {
                every {
                } throws UserNotFoundException()

                shouldThrow<UserNotFoundException> {


Kotest offers serveral different styles to write tests. I chose the FunSpec style for this example. You could also use a BDD-like or Jasmine-like style, if you want to.

It’s much more intuitive to nest tests with Kotest. To be fair, JUnit 5 allows you to use @Nested with an inner class to acomplish nesting as well, but it’s not as intuitive and harder to read than trailing lambdas.

Assertions are also a little easier to write. Kotest has over 100 different matchers. You can use them as extensions functions, like in the example above or alternatively as infix functions, for example service.getUser(1) shouldBe user. It’s also quite simple to write custom matchers.

There are many more features like soft assertions, tagging, easy temporay file creation or handling for non-deterministic test cases.

For mocking we decided to use MockK, since it’s way more intuitive to use than Mockito with Kotlin. Don’t get me wrong, Mockito is a great library, but it has one flaw: the when method. when is a keyword in Kotlin and to use it with Mockito, you need to write it in backticks. That’s quite ugly and not intuitive at all.

So, in summary, Kotest offers a bunch of pretty neat features and is very intuitive to use. Of course, JUnit can achieve much of this as well, it’s just not that shiny and little harder to read.

The actual problem or: why the frak is there IntelliJ in the title?

If you migrate an old codebase to Kotlin and want to use Kotest, you will have no choice and have to use JUnit and Kotest in coexistence.

That shouldn’t be a problem, since Kotest uses the JUnit 5 Jupiter engine under the hood. But …

As I wrote a new service in Kotlin and some tests with Kotest, I could not start the JUnit tests anymore. As soon as the maven depedency of Kotest was present, IntelliJ didn’t recognize JUnit tests and used the Kotest files only. With mvn test (Maven Surefire) everything worked fine.

I tried several things and was ready to give up. Search engines didn’t find anything about this problem. Nothing on GitHub, nothing on Stack Overflow.

I hate to give up, so I decided to create a small demo project to open a GitHub issue. Well, that didn’t work out as intended, since the discovery of JUnit tests in the demo project worked fine. The IntelliJ JUnit runner did what it was supposed to: run JUnit and Kotest.

Well, frak. There must be some kind configuration problem with my real project. I already looked at the IntelliJ runner config, Maven files etc. — nothing worked.

I compared the Maven files from both codebases and there was one difference: my real project did not include the JUnit Jupiter Engine depedency. Bingo. I added to the Maven file and guess what? It worked like a charm.

What an embarassment. As we upgraded from JUnit 4 to 5, we forgot to add the new depedency for the engine. I don’t know why the tests worked at all, but it seems the engine is not really necessary for all cases. But it can screw up test discovery quite well, if you forget it.

The dependency configuration in POM file should like this:


Well, that was a long post, but IMO it was necessary to show how this problem came to be and even if you have no trouble at all, perhaps you’ll consider to use Kotest. It’s awesome!


I’m not sure, but this issue could happen with Gradle as well, when you are migrating to JUnit 5.

macOS Catalina EDID Override AKA HDMI color fix

HDMI connections from your Mac to monitor can be a pain in the ass. There is a chance that macOS will detect your monitor as a TV and set the color space to YCbCr. You will get wrong colors and sometimes blurry fonts.

If you’re having this problem, like me, you know the fix: a patched EDID created with this little Ruby script.

The installation of this EDID override could be tedious since the release of El Capitan, as SIP won’t let you access the necessary system files. Just disable it in recovery mode, copy the file and enable it again. Sucks, but works just fine.

Now, Catalina is out for a few hours and has a new way to annoy people who need EDID overrides. All system-related directories and files are read-only, regardless of the status of SIP.

Fortunately Apple was not crazy enough to disable the write access completely.

Help is on the way, ETA 0 seconds!

  1. Patch your EDID
  2. Boot into recovery mode with CMD+R
  3. Login with your user
  4. Open Disk Utility, select your volume (in most cases Macintosh HD) and mount it with your password (yes, again)
  5. Close the Disk Utility and open a Terminal window
  6. Copy the directory with the patched EDID to /Volumes/$VOLUME_NAME/System/Library/Displays/Contents/Resources/Overrides
  7. Reboot and enjoy the right colors again

Here’s an example of the shell commands:

cd /Volumes/Macintosh\ HD/System/Library/Displays/Contents/Resources/Overrides
cp -rf /Volumes/Macintosh\ HD/Users/webcodr/DisplayVendorID-5a63 .

Don’t forget to use the correct volume and user names!

Dear Apple

Just add a simple solution to select the HDMI color space. A simple shell commando with sudo would suffice or at least let us use an override directory within the user library as it was possible many years ago. It just sucks to do this after every macOS upgrade and every time you improve system security, it gets harder.

Please, don’t forget us powers users …

Hello, Dark Mode

Dark mode for Android and iOS? Hold my beer …

It’s quite simple to implement. Every modern browser can evaluate media queries in JavaScript with window.matchMedia() and supports CSS variables.

I added the following to my application JavaScript file:

const preferColorSchemeResult
  = window.matchMedia('(prefers-color-scheme: dark)')

if (preferColorSchemeResult && preferColorSchemeResult.matches === true) {
  document.documentElement.setAttribute('data-theme', 'dark')
} else {
  document.documentElement.setAttribute('data-theme', 'light')

The script will set the data attribute theme on the document element (html) with the possible values dark or light depending on the result of the media query.

There’s no need for a polyfill, even IE 10 supports window.matchMedia()

Stylesheet changes is even simpler, since I already had introduced SCSS color variables a while ago. I just had to replace them with CSS variables.

// colors
$c_white: #fff;
$c_dark-grey: #4A4A4A;

:root {
  --container-background-color: #{$c_white};

[data-theme="dark"] {
  --container-background-color: #{darken($c_dark-grey, 20%)};

That’s basically it. If you use SCSS, please take notice to use interpolations to map the SCSS variables to CSS variables. This change in SassScript expressions was necessary to provide full compatibility with plain CSS.

Since the theme selection is fully automated, I will provide a toggle possibiliry in a future release for those of you who prefer the light mode. This can be easily achieved with a flag in local storage and some minor changes in the JavaScript part.

Snapshot Tests With Jest

Writing tests can sometimes be a tedious task. Mocks and assertions can be a pain in the ass. The latter is especially nasty when HTML is involved. Give me the second p element from the 30th div within an article in aside etc. – no thanks.

The creators of Jest (Facebook) have found a better way: Snapshot tests!

How does it work?

Take a look the following assertion:

it('should create a foo bar object', () => {
  const result = foo.bar()

toMatchSnapshot() takes what ever you give to expect(), serializes it and saves it into a file. The next test run will compare the expected value to the stored snapshot and will fail if they don’t match. Jest shows a nicely formatted error message and diff view on failed tests.

This is really useful with generated HTML and/or testing UI behaviour. Just call the method and let it compare to the snapshot.

Updating snapshots

You added something to your code and the snapshot has to be updated? No problem:

jest --updateSnapshot

If you’re using the Jest watcher it’s even simpler. Just press u to update all snapshots or press i to update the snapshots interactively.

What about objects with generated values?

Here’s an example with an randomized id:

it('should fail every time', () => {
  const ship = {
    id: Math.floor(Math.random() * 20),
    name: 'USS Defiant'


The id will change on every test run, so this test will fail every time. Well, shit? Nope. Jest got you covered:

it('should create a ship', () => {
  const ship = {
    id: Math.floor(Math.random() * 20),
    name: 'USS Defiant'

    id: expect.any(Number)

Jest will now only compare the type of the id and the test will pass.

For certain objects like a date, there is another possibility:

Date.now = jest.fn(() => 1528902424828)

A call of Date.now() will call the mock method and always return the same value.

Some advice

  1. Always commit your snapshots! If they are missing,CI systems will always create new snapshots and the tests will become useless.

  2. Snapshot tests are an awesome tool, but don’t be too lazy. They are no replacement for other assertion types, especially if you’re working test-driven. Rather use them alongside with your other tests.

  3. Write meaningful test names. Well, you heard that one before, didn’t you? Really, it helps a a lot when tests fail or you have to look inside a snapshot file. Jest takes a test name as an id inside a snapshot file. That’s why you have to update a snapshot after changing the name.

Introducing DeliveryGuy

I like the Fetch API. It’s supported by all modern browsers, easy to use and has some really good polyfills for older devices. But Fetch has one major flaw: it will only throw errors if there is a network problem.

That’s a really odd decision from my point of view. Nearly any HTTP library out there throws errors or rejects the promise in case of a HTTP error.

A Fetch response object has the property ok to determine if the server responded with an error, but that’s not very comfortable to use.

Since my team and I have decided to use Fetch in a Vue-based web app, I decided to create a little wrapper for much more convenience. Say hello to DeliveryGuy.


Well, surprise, it’s a Node module, so just use any package manager you like. My personal choice is yarn.

yarn install delivery-guy


import { deliverJson } from 'delivery-guy'

const getItems = async () => {
  try {
    const items = await deliverJson('/api/items')
  } catch (e) {
    console.log('HTTP Status', e.response.status)
    console.log('Response Body'. e.responseBody)

What’s going on here?

DeliveryGuy exports two main functions:

  • deliver() will return a response promise like fetch() does.
  • deliverJson() presumes your response body contains JSON. It’s basically a shortcut and returns the promise of Response.json().

Both will accept the same two parameters as fetch() does and pass them along.

If the server responds with a HTTP error, DeliveryGuy will throw an error.

Due to the inheritance limitations of built-in classes with ES5 I mentioned in my last post, it’s only possible to set custom properties of a custom error class.

DeliveryGuy provides additional two properties on an error object:

  • response has the original response object of a Fetch call.
  • responseBody contains the response body and will try to parse it as JSON. If JSON.parse fails, it will return the response body in its original state.


DeliveryGuy allows you comfortably call the Fetch API without a hassle on HTTP errors. Just use try/catch and you’re done.

Please let me know on GitHub if you have feedback, a feature request or found a bug. Thank you!

Why custom errors in JavaScript with Babel are broken

Have you ever tried to write a custom error class in JavaScript? Well, it does work to a certain extend. But if you want to add custom methods or call instanceof to determine the error type it will not work properly.

Here is a little example of a custom error class:

class MyError extends Error {
  constructor(foo = 'bar', ...params) {
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, MyError)
    this.foo = foo
  getFoo() {
    return this.foo

try {
  throw new MyError('myBar')
} catch(e) {
  console.log(e instanceof MyError) // -> false
  console.log(e.getFoo()) // -> Uncaught TypeError: e.getFoo is not a function

Works fine in any browser with ES6/ES2015 support, but if you transpile the example with Babel to ES5 and execute the code, you will get the results shown in the comments.


Due to limitations of ES5 it’s not possible to inherit from built-in classes like Error, see the Babel docs.

Possible solution

The docs mention a plug-in called babel-plugin-transform-builtin-extend to resolve this issue, but if you have to support older browsers it may not help. In order to work the plug-in needs support for __proto__. Take a guess which browser does not support __proto__ … and of course, it’s the web developers best friend aka Internet Explorer. Thankfully it affects only version 10 and below.


If it’s not feasable to use the plug-in, you can at least access properties set in the constructor. A call to e.foo in the example is possible, but e instanceof MyError will return false, since you will always get an instance of Error.


Nothing of this ideal. We have to wait until it’s possible to use ES6/ES2015 directly. Yes, we all could set our transpile targets to ES6/ES2015 today, but our clients usually won’t allow it. Some customer is always browsing the web with an ancient device/browser.

Awesome tests with Vue and Jest

Jest is a very neat JavaScript testing library from Facebook. It’s mostly syntax-compatible with Jasmine and needs zero or very less configuration. Code coverage reports are there out-of-the-box and with sandboxed tests and snapshot testing it has some unique features.

Set-up Jest


You are using Vue CLI? Consider yourself lucky, the set-up of Jest could not be simpler:

yarn add --dev jest @vue/cli-plugin-unit-jest
vue invoke unit-jest

Vue CLI will do the rest and also create an example spec for the HelloWorld component.


Install all necessary dependencies:

yarn add --dev @vue/test-utils babel-jest jest jest-serializer-vue vue-jest

Create jest.config.js in your project root directory:

module.exports = {
  moduleFileExtensions: ['js', 'json', 'vue'],
  transform: {
    '^.+\\.vue$': 'vue-jest',
    '^.+\\.js?$': 'babel-jest'
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1'
  snapshotSerializers: ['jest-serializer-vue'],
  testMatch: ['<rootDir>/src/tests/**/*.spec.js']

Please adjust the paths in moduleNameMapper and testMatch to your project.

You should also add a modern JavaScript preset to your .babelrc file:

  "presets": ["es2015"]

Optional step

Add the following line to your .gitignore file:


The set-up is now complete. Let’s write a test file!

Writing tests

Here is a litte example Vue component:

  <ul class="demo">
    <li class="demo__item" v-for="value in values" :key="value">{{ value }}</li>

export default {
  name: 'demo',
  data () {
    return {
      values: []
  created() {
  methods: {
    async fetchValues() {
      const response = await fetch('/api/demo/values')
      this.values = await response.json()

The component Demo will fetch some values after its creation and display them in an unordered list. This is example is quite simple, but testing is a little more complex due to the usage of fetch, async and await.

Of course, there are some tools to help us:

yarn add --dev fetch-mock flush-promises

Now, let’s write a test:

import { shallow } from '@vue/test-utils'
import fetchMock from 'fetch-mock'
import flushPromises from 'flush-promises'
import Demo from '@/components/Demo.vue'

const values = [

describe('Demo.vue', () => {
  beforeEach(() => {
    fetchMock.get('/api/demo/values', values)

  it('renders component', async () => {
    const wrapper = shallow(Demo)
    await flushPromises()


  afterEach(() => {

What’s going on?

  1. shallow from Vue Test Utils creates a wrapper of the rendered and mounted component, any child components will be stubs. If you need child components in your test, please use mount instead of shallow.

  2. fetchMock will create a mocked version of the Fetch API. In this case it will return the defined values for a GET request to /api/demo/values. If you send a request that’s not defined in fetchMock, it will throw an exception and break your tests.

  3. The test itself is defined as async to use await for flushPromises(). It will wait until the mocked request is finished and the values are stored in the component’s data.

  4. You can now access the data property values and compare the content to the response of the mocked HTTP request.


Setting up Jest for Vue is easy, even if you have to do it manually.

A little warning: setting up Jest for an existing app can be tedious. The current AngularJS app of our customer can’t be tested with Jest, at least for now. The AngularJS HTTP mock does not work and I haven’t figured out the problem yet.

But enough of Angular: the real deal comes with the testing itself. Async/await is a nice and simple way for testing asynchronous behaviour. I don’t think this could be easier and it’s a reliable method with the power of modern JavaScript. Try to imagine what the demo test would look like in ES5 …