How to inject localStorage into jest tests


#1

I have a service that accesses localStorage, which I inject into other services.

I can’t work out how to “initialize” this service when testing the services I am injecting this one into

The service I am testing

@autoinject
export class PrintContractService {
    public contract: IContract;
    public PRINT_CONTRACT_KEY = "printContractKey";

    constructor(
        private readonly commissionService: CommissionService,
        private readonly localStorageService: LocalStorageService
    ) { }

    public setContract(contract: IContract) {
        this.contract = contract;
        this.localStorageService.set(this.PRINT_CONTRACT_KEY, contract);
    }
}

I learned that to initialize HttpClient in my test I should do

beforeEach(()=>{
    auContainer = new Container();
    auContainer.registerInstance(HttpClient);
    sut = auContainer.get(MyService);
})

I’m not interested in the results of setContract(contract:IContract), so I don’t have to mock localStorage.

I just want to test the other methods in the service.

Having said that, I know that I can create a mock of localService as:

const mockLocalService = {
    set:jest.fn(),
    get:jest.fn()
    etc...

but I don’t understand how I tell auContainer to inject it into the service.


#2

You can try:

import { AppStorage } from './somewhere';

@autoinject
export class LocalStorageService {
  constructor(storage: AppStorage) {
    this.storage = storage;
  }

  public set(key: string, value: any) {
    this.storage.setItem(key, JSON.stringify(value);
  }
}

Then in entry of test, you can do

auContainer.registerInstance(AppStorage, {
  getItem() {}
  setItem() {}
});

And in your real app code, you can do

aurelia.use.instance(AppStorage, localStorage);

If you have already tried this then maybe post some more details why it didn’t work for you?

Edit:

// Or just a string:
@inject('AppStorage')
export class LocalStorageService {
  constructor(storage: Storage) { // storage is built in interface for local storage
    this.storage = storage;
  }

  public set(key: string, value: any) {
    this.storage.setItem(key, JSON.stringify(value);
  }
}

// 
auContainer.registerInstance('AppStorage', mockStorage);

#3

Thanks for that

I ended up with

describe("PrintContractService", () => {
    let auContainer: Container;
    let sut: PrintContractService;

    beforeEach(() => {
        const mockLocalStorageService = {
            get() { return null; },
            set() { return null; },
        };

        auContainer = new Container();
        auContainer.registerInstance(HttpClient);
        auContainer.registerInstance(LocalStorageService, mockLocalStorageService);
        sut = auContainer.get(PrintContractService);
    });
}

Is there any documentation that explains this? All the document on this site refers to mocking components (which I haven’t yet got to). Nowhere could I find on Google an explanation of how to test a service and ignore its ctor dependencies, with the exception of registering an HttpClient instance.

I love aurelia, but some things are just so damn difficult.

This is my first project where I have gone beyond the basics. It is fun learning about how to do things differently and more effectively, but some things (such as testing) are just so frustrating.

Again, many thanks for your help (as ever).

Jeremy


#4

The issue you described above is how DI works, I’m happy to answer your Q about it, but I’m not sure how to help you get through it in the most appropriate way. I learnt it through trial and errors. Maybe you can start with https://aurelia.io/docs/fundamentals/dependency-injection#introduction, don’t hesitate to come back here with Q, i’m pretty sure folks will be happily helping you out.


#5

Oh - I hope I didn’t sound like I was being critical.

I have gone through that article - but I guess I didn’t fully understand it :grin:

I am enormously grateful for the support here - every one of my questions gets answered almost immediately.

I’m thinking of putting together a brief article detailing my experiences with Aurelia, and what I have learned on my journey.

I am not a “pro” and no longer a complete “nooby”. The thing is that as I learn a bit more I start to rush ahead.

As you yourself say here - you “learned it through trial and error”. And that’s coming from a pro! Imagine us poor amateurs!!!

Again - a million thanks to everyone here who are so incredibly helpful.

Jeremy


#6

I just had a similar situation in the Store plugin where I’ve just mocked the localStorage all together. Take a look here somewhere around the end. https://github.com/aurelia/store/blob/master/test/unit/middleware.spec.ts

localStorage is imported directly via Platform.global so that it works also in node environments like jest