How to mock window.top and window.self with Jest
If you're working with iframes to embed web content in other pages, your code might include checks like this:
if (window.top === window.self) {
// Code here would be executed when not in an iframe
console.log('We are not in an iframe!');
} else {
// Code here would be executed when in an iframe
console.log('We ARE in an iframe!');
}
What exactly is this code doing?
Detecting if you're inside an iframe
In a non-iframe situation, you have just one window
object. You probably already know it as the topmost DOM element.
But when you're using iframes on a web page, you have a nested hierarchy of window
objects instead; each iframe contains its own DOM, so there's one window
at the top of each iframe.
When you have JavaScript running in any of these frames, you can reference window
properties like top
and self
to tell where you are in the hierarchy:
window.self
returns the current window itselfwindow.parent
returns the immediate parent of the current windowwindow.top
returns the topmost window in the hierarchy
Assuming we had a script running in the innermost frame, here's a visual example of that hierarchy:
So that's how window.top === window.self
checks if the current context is an iframe, or the top level window.
Unit testing iframe checks with Jest
In Jest's test environment, your unit test code won't actually be running inside an iframe. So how can you test both branches of that kind of if/else statement?
Here's a simple example of the above check in a function:
function isInIframe() {
return window.top !== window.self;
}
It should return true
if in an iframe, and false
if not. I'll use it to demonstrate how you can mock the relevant properties in your tests.
Testing the positive (in iframe) case
Here's how we can test that this function returns true
if executed in an iframe:
describe('isInIframe', () => {
it('should return true if in iframe', () => {
// Extract the real properties so we can put them back after this test
const { top, self } = window;
// Delete the real properties from window so we can mock them
delete window.top;
delete window.self;
// Mock window.top and window.self as different objects (we don't
// need anything else from the original objects here, so {} is fine)
window.top = {};
window.self = {};
// Test the code
const result = isInIframe();
expect(result).toBe(true);
// Restore the original values so other tests will function normally
window.top = top;
window.self = self;
});
});
Why does this work?
In JavaScript, objects are pass by reference. This means two empty objects can have the same value—they are both empty objects—but don't pass an equality check, because they have a different reference, or location in memory.
So we can use the fact that {} !== {}
to force unit tests to execute the 'in iframe' branch.
In a simple test, it's enough to just use empty objects. But what if you need to access the other properties on either of the window objects?
You can make a copy of each of them (keeping the values but forcing each object to have a new reference) by destructuring:
window.top = {...window.top};
window.self = {...window.self};
Testing the negative (in top window) case
The non-iframe case is a lot simpler, because it's actually just the default behavior of the test environment:
describe('isInIframe', () => {
it('should return false if not in iframe', () => {
const result = isInIframe();
expect(result).toBe(false);
});
});