Proxy Objects for Quick and Dirty Service Mocks in Jest

Zak Barbuto
NextFaze
Published in
3 min readSep 6, 2019

Edit 2023–07–05 — Added some non-screenshot code snippets and tidied up the implementation a bit.

So you’re writing a test for your Angular component, provider, effect, whatever. It has a bunch of dependencies injected into. Now, assume we’re doing shallow testing so we want to quickly stub out these services to just write tests focusing on the implementation of the thing you’re testing. Note: for now, we’re going to ignore the arguments of whether or not this is actually good practice here.

You might start by doing something like this:

Stubbing out services with plain JavaScript objects

This works fine but it quickly bloats out your test files with unnecessary stub code. Moreover, you might end up repeating these mocks all over the place.

A much quicker, easier and more re-usable alternative is to use JavaScript’s proxy object. We can define an arbitrary getter which will return a new jest spy (jest.fn()) and store that for later retrieval (so multiple calls will always call the same stub). It looks something like this:

Service mock factory

Any method we try to call on an object created from serviceMockFactory() will return a jest.fn() — a different one for each method but always the same one for the same method. This also means we’re free to do all the useful jest mocking goodies like mockImplementation. We can verify the factory with a quick test:

Testing our service mock factory

Now — we can replace all of our quick mocks from the first example with a reference to this service mock factory:

Using our service mock factory to stub out our services

We could shorthand this even further to serviceMockForToken = token => ({ provide: token, useFactory: serviceMockFactory})

Quickly stubbing out several providers

We’re left with one small problem though: we’re only stubbing methods. What if we want to access a property on the service? This can be fixed by adding a setter to our proxy that allows us to override any of the keys with our own value:

Setter for overriding values

Now, we can override a specific property in any given test:

TestBed.get(MyService).someProperty = of('Value has been mocked!')

And likewise if we wanted to mock the return value of a method we need only to mockImplementation or mockReturnValue on one of the jest functions.

If you’re not using this for Angular specifically, it’s probably worth renaming it to something more generic. In this case, I’m using createSpyObject (similar to createSpyObj provided by jasmine )

export const createSpyObject = () => new Proxy(
{ proxies: {} },
{
get(target, name) {
target.proxies ??= {};
target.proxies[name] ??= jest.fn();
return target.proxies[name];
},
set(target, key, value) {
target.proxies[key] = value;
return true;
},
}
);

Or, if you want something type-safe:

export type SpyObject<T> = {
[key in keyof T]: jest.SpyInstance;
};

export function createSpyObject<T = any>() {
return new Proxy(
{ proxies: {} },
{
get(target, name) {
target.proxies ??= {};
target.proxies[name] ??= jest.fn();
return target.proxies[name];
},
set(target, key, value) {
target.proxies[key] = value;
return true;
},
}
) as SpyObject<T>;
}

Done — simple mocking for any service. What’s better — we can re-use this util all over our application’s tests. Easy!

--

--