Friday evening at 5pm on my way out of work I was about to push a feature fix up to add some encryption and decryption using crypto-js. The API looks like this:

import CryptoJS from 'crypto-js'

const encryptedThing = CryptoJS.AES.encrypt("message", SECRET_KEY).toString()
const decryptedThing = CryptoJS.AES.decrypt(encryptedThing, SECRET_KEY).toString(CryptoJS.enc.Utf8)

It’s an AES cipher block that puts a message in a cipher, and decrypts it on the other end. Cool. It uses math, like popular cryptographic technology Bitcoin, so you know it’s good.

As I was ready to push the code up, my tests were failing in the prepush hook. I knew the code was working but I’d have to appease Jest. Jest’s issue was it didn’t know about the import CryptoJS from 'crypto-js' part because it only understands require(). I’ve dealt with this problem before. I had some other module mocks in that file, so I copied them verbatim.

// This didn't work.
const mockAESInstance = {
	decrypt: jest.fn().mockReturnThis(),
}

jest.mock('crypto-js', () => {
	return {
		AES: jest.fn(() => mockAESInstance)
		...
	}
})

It didn’t work. Jest kept saying decrypt is not a function. I’ve also dealt with this problem before. So I tried…

  • decrypt: jest.fn() - surely this is a function? nope.
  • decrypt: jest.fn().mockReturnValue('decryptedThing') = also not a function
  • And then I tried every combination based on every amalgamation of every StackOverflow answer I could find to mock a nested object function.

…and it still didn’t work.

I used VS Code’s Intellisense to dig into the types inside CryptoJS. CryptoJS’s AES.decrypt returns a custom type called a WordArray that has a toString() method. Ah ha! Foolish me to think toString() was the native Array.toString() function! I tried mocking the toString() and CryptoJS.enc.Utf8 in a variety of ways but couldn’t get Jest to mock a nested function of a nested object function…

Fuck it, just use Copilot

It was now 10pm. There was dinner. There was watching TV with the kids. There was bed time. But I was noodling on my laptop like a deadbeat dad because I needed Jest to allow this one line of code in my commit. In desperation, I deleted all my attempts and sent a prayer to the AI gods (who were unhelpful up to this point) and kept hitting tab… Copilot summoned this:

// Final Fix 
jest.mock('crypto-js', () => {
	return {
		AES: {
			decrypt: jest.fn().mockReturnValue({
				toString: jest.fn().mockReturnValue('decryptedThing')
			})
		}
		...
	}
})

And it worked! Looking at the final answer, I can see how this appeases Jest, but I ashamedly admit that I would have never got there on my own. A double fn().mockReturnValue(). This situation is an embodiment of my biggest frustration with Jest. The ESM imports thing is big and makes me want to switch to Vitest today, but the biggest frustration is that you spend so much time and energy mocking away Jest’s deficiencies.

Anyways, thanks Copilot for solving the problem my powerless human brain could never answer. In my review of Copilot last year I mentioned using it for writing tests and it’s still proving valuable there. It’s solving the “fear of the blank page” problem. With a little coaxing, I’m able to get 100% coverage on my components for props and conditionals. Copilot seems okay at that. Little less great at mocks, but it can do it. Sometimes the robot is wrong though, so you have pay attention to what the machine is spitting out.

If you’re on the fence about Copilot, I’d recommend it for the sole purpose of having someone else around who will throw spaghetti when you’re tired of throwing spaghetti at the wall. It makes the job a little easier and can unblock you.