Làm thế nào để tôi đối phó với localStorage trong các bài kiểm tra jest?


144

Tôi tiếp tục nhận được "localStorage không được xác định" trong các bài kiểm tra Jest có ý nghĩa nhưng lựa chọn của tôi là gì? Đánh tường gạch.

Câu trả lời:


141

Giải pháp tuyệt vời từ @chiedo

Tuy nhiên, chúng tôi sử dụng cú pháp ES2015 và tôi cảm thấy nó gọn gàng hơn khi viết theo cách này.

class LocalStorageMock {
  constructor() {
    this.store = {};
  }

  clear() {
    this.store = {};
  }

  getItem(key) {
    return this.store[key] || null;
  }

  setItem(key, value) {
    this.store[key] = value.toString();
  }

  removeItem(key) {
    delete this.store[key];
  }
};

global.localStorage = new LocalStorageMock;

8
Có lẽ nên làm value + ''trong trình thiết lập để xử lý chính xác các giá trị null và không xác định
menehune23

Tôi nghĩ rằng trò đùa mới nhất chỉ là sử dụng đó || nulllà lý do tại sao bài kiểm tra của tôi thất bại, bởi vì trong bài kiểm tra của tôi, tôi đã sử dụng not.toBeDefined(). Giải pháp @Chiedo làm cho nó hoạt động trở lại
jcubic

Tôi nghĩ rằng về mặt kỹ thuật này vẫn còn sơ khai :) xem ở đây để biết phiên bản bị nhạo báng: stackoverflow.com/questions/32911630/
Kẻ

100

Tìm ra nó với sự giúp đỡ từ điều này: https://groups.google.com/forum/#!topic/jestjs/9EPhuNWVYTg

Thiết lập một tệp với các nội dung sau:

var localStorageMock = (function() {
  var store = {};
  return {
    getItem: function(key) {
      return store[key];
    },
    setItem: function(key, value) {
      store[key] = value.toString();
    },
    clear: function() {
      store = {};
    },
    removeItem: function(key) {
      delete store[key];
    }
  };
})();
Object.defineProperty(window, 'localStorage', { value: localStorageMock });

Sau đó, bạn thêm dòng sau vào gói.json theo cấu hình Jest của bạn

"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",


6
Rõ ràng với một trong những cập nhật, tên của tham số này đã thay đổi và bây giờ nó được gọi là "setupTestFrameworkScriptFile"
Grzegorz Pawlik

2
"setupFiles": [...]làm việc tốt Với tùy chọn mảng, cho phép tách các giả thành các tệp riêng biệt. Ví dụ:"setupFiles": ["<rootDir>/__mocks__/localStorageMock.js"]
Stiggler

3
Giá trị trả về của getItemhơi khác với giá trị mà trình duyệt sẽ trả về nếu không có dữ liệu nào được đặt theo một khóa cụ thể. gọi getItem("foo")khi nó không được đặt sẽ trở lại nulltrong trình duyệt, nhưng undefinedbằng giả này - điều này đã khiến một trong những thử nghiệm của tôi thất bại. Giải pháp đơn giản đối với tôi là trở lại store[key] || nulltrong getItemchức năng
Bến Broadley

điều này không hiệu quả nếu bạn làm một cái gì đó nhưlocalStorage['test'] = '123'; localStorage.getItem('test')
cướp

3
Tôi nhận được lỗi sau - giá trị jest.fn () phải là hàm giả hoặc gián điệp. Có ý kiến ​​gì không?
Paul Fitzgerald

55

Nếu sử dụng ứng dụng tạo phản ứng, có một giải pháp đơn giản và dễ hiểu hơn được giải thích trong tài liệu .

Tạo src/setupTests.jsvà đặt cái này trong đó:

const localStorageMock = {
  getItem: jest.fn(),
  setItem: jest.fn(),
  clear: jest.fn()
};
global.localStorage = localStorageMock;

Tom Mertz đóng góp trong một bình luận dưới đây:

Sau đó, bạn có thể kiểm tra các hàm localStorageMock của mình được sử dụng bằng cách thực hiện một số thứ như

expect(localStorage.getItem).toBeCalledWith('token')
// or
expect(localStorage.getItem.mock.calls.length).toBe(1)

bên trong các bài kiểm tra của bạn nếu bạn muốn chắc chắn rằng nó được gọi. Hãy xem https://facebook.github.io/jest/docs/en/mock-fifts.html


Chào c4k! Bạn có thể vui lòng cho một ví dụ về cách bạn sẽ sử dụng nó trong các bài kiểm tra của bạn?
Dimo

Ý anh là gì ? Bạn không phải khởi tạo bất cứ điều gì trong các thử nghiệm của mình, nó chỉ tự động chế giễu localStoragebạn sử dụng trong mã của mình. (nếu bạn sử dụng create-react-appvà tất cả các tập lệnh tự động mà nó cung cấp một cách tự nhiên)
c4k

Sau đó, bạn có thể kiểm tra các hàm của localStorageMock của bạn được sử dụng bằng cách thực hiện một cái gì đó giống như expect(localStorage.getItem).toBeCalledWith('token')hoặc expect(localStorage.getItem.mock.calls.length).toBe(1)bên trong các bài kiểm tra của bạn nếu bạn muốn đảm bảo rằng nó được gọi. Hãy xem facebook.github.io/jest/docs/en/mock-fifts.html
Tom Mertz

10
vì điều này tôi nhận được một lỗi - giá trị jest.fn () phải là hàm giả hoặc gián điệp. Có ý kiến ​​gì không?
Paul Fitzgerald

3
Điều này có gây ra vấn đề gì không nếu bạn có nhiều bài kiểm tra sử dụng localStorage? Bạn có muốn đặt lại các điệp viên sau mỗi bài kiểm tra để ngăn chặn "sự lan tỏa" sang các bài kiểm tra khác không?
Cá tầm Brandon

43

Hiện tại (19 tháng 10) localStorage không thể bị chế giễu hoặc theo dõi bởi jest như bạn thường làm, và như được nêu trong các tài liệu ứng dụng tạo phản ứng. Điều này là do những thay đổi được thực hiện trong jsdom. Bạn có thể đọc về nó trong trình theo dõi vấn đề jestjsdom .

Thay vào đó, bạn có thể theo dõi nguyên mẫu:

// does not work:
jest.spyOn(localStorage, "setItem");
localStorage.setItem = jest.fn();

// works:
jest.spyOn(window.localStorage.__proto__, 'setItem');
window.localStorage.__proto__.setItem = jest.fn();

// assertions as usual:
expect(localStorage.setItem).toHaveBeenCalled();

Trên thực tế, nó hoạt động với tôi chỉ với spyOn, không cần ghi đè chức năng setItemjest.spyOn(window.localStorage.__proto__, 'setItem');
Yohan Dahmani

Vâng, tôi liệt kê hai cái là lựa chọn thay thế, không cần phải làm cả hai.
Bastian Stein

ý tôi là không có sự ghi đè của setItem 😉
Yohan Dahmani

Tôi không nghĩ tôi hiểu. Bạn có thể làm rõ?
Bastian Stein

1
À đúng rồi. Tôi đã nói rằng bạn có thể sử dụng dòng đầu tiên hoặc dòng thứ hai. Họ là những lựa chọn thay thế làm điều tương tự. Dù sở thích cá nhân của bạn là gì :) Xin lỗi về sự nhầm lẫn.
Bastian Stein


13

Một sự thay thế tốt hơn giúp xử lý undefinedcác giá trị (nó không có toString()) và trả về nullnếu giá trị không tồn tại. Đã thử nghiệm điều này với reactv15 reduxredux-auth-wrapper

class LocalStorageMock {
  constructor() {
    this.store = {}
  }

  clear() {
    this.store = {}
  }

  getItem(key) {
    return this.store[key] || null
  }

  setItem(key, value) {
    this.store[key] = value
  }

  removeItem(key) {
    delete this.store[key]
  }
}

global.localStorage = new LocalStorageMock

Cảm ơn Alexis Tyler về ý tưởng để thêm removeItem: developer.mozilla.org/en-US/docs/Web/API/Storage/removeItem
Dmitriy

Tin rằng null và không xác định cần phải dẫn đến "null" và "không xác định" (chuỗi ký tự)
menehune23

6

Nếu bạn đang tìm kiếm một bản giả và không phải là sơ khai, đây là giải pháp tôi sử dụng:

export const localStorageMock = {
   getItem: jest.fn().mockImplementation(key => localStorageItems[key]),
   setItem: jest.fn().mockImplementation((key, value) => {
       localStorageItems[key] = value;
   }),
   clear: jest.fn().mockImplementation(() => {
       localStorageItems = {};
   }),
   removeItem: jest.fn().mockImplementation((key) => {
       localStorageItems[key] = undefined;
   }),
};

export let localStorageItems = {}; // eslint-disable-line import/no-mutable-exports

Tôi xuất các mục lưu trữ để khởi tạo dễ dàng. IE Tôi có thể dễ dàng đặt nó vào một đối tượng

Trong các phiên bản mới hơn của Jest + JSDom, không thể thiết lập điều này, nhưng bộ lưu trữ cục bộ đã có sẵn và bạn có thể theo dõi nó như vậy:

const setItemSpy = jest.spyOn(Object.getPrototypeOf(window.localStorage), 'setItem');

5

Tôi tìm thấy giải pháp này từ github

var localStorageMock = (function() {
  var store = {};

  return {
    getItem: function(key) {
        return store[key] || null;
    },
    setItem: function(key, value) {
        store[key] = value.toString();
    },
    clear: function() {
        store = {};
    }
  }; 
})();

Object.defineProperty(window, 'localStorage', {
 value: localStorageMock
});

Bạn có thể chèn mã này vào setupTests và nó sẽ hoạt động tốt.

Tôi đã thử nghiệm nó trong một dự án với kiểu chữ.


Đối với tôi Object.defineProperty đã thực hiện thủ thuật này. Phân công đối tượng trực tiếp không hoạt động. Cảm ơn!
Vicens Fayos

4

Thật không may, các giải pháp mà tôi tìm thấy ở đây không hiệu quả với tôi.

Vì vậy, tôi đã xem xét các vấn đề của Jest GitHub và tìm thấy chủ đề này

Các giải pháp được đánh giá cao nhất là những giải pháp sau:

const spy = jest.spyOn(Storage.prototype, 'setItem');

// or

Storage.prototype.getItem = jest.fn(() => 'bla');

Các xét nghiệm của tôi không có windowhoặc Storageđược xác định. Có lẽ đó là phiên bản cũ hơn của Jest tôi đang sử dụng.
Antrikshy

3

Như tài liệu đề xuất @ ck4 có giải thích rõ ràng cho việc sử dụng localStoragetrong jest. Tuy nhiên, các chức năng giả đã không thực hiện bất kỳlocalStorage phương thức nào.

Dưới đây là ví dụ chi tiết về thành phần phản ứng của tôi, sử dụng các phương thức trừu tượng để ghi và đọc dữ liệu,

//file: storage.js
const key = 'ABC';
export function readFromStore (){
    return JSON.parse(localStorage.getItem(key));
}
export function saveToStore (value) {
    localStorage.setItem(key, JSON.stringify(value));
}

export default { readFromStore, saveToStore };

Lỗi:

TypeError: _setupLocalStorage2.default.setItem is not a function

Khắc phục:
Thêm chức năng giả bên dưới cho jest (đường dẫn .jest/mocks/setUpStore.js:)

let mockStorage = {};

module.exports = window.localStorage = {
  setItem: (key, val) => Object.assign(mockStorage, {[key]: val}),
  getItem: (key) => mockStorage[key],
  clear: () => mockStorage = {}
};

Đoạn trích được tham chiếu từ đây


2

Riff tắt một số câu trả lời khác ở đây để giải quyết nó cho một dự án với Typecript. Tôi đã tạo một LocalStorageMock như thế này:

export class LocalStorageMock {

    private store = {}

    clear() {
        this.store = {}
    }

    getItem(key: string) {
        return this.store[key] || null
    }

    setItem(key: string, value: string) {
        this.store[key] = value
    }

    removeItem(key: string) {
        delete this.store[key]
    }
}

Sau đó, tôi đã tạo một lớp LocalStorageWrapper mà tôi sử dụng cho tất cả quyền truy cập vào bộ nhớ cục bộ trong ứng dụng thay vì truy cập trực tiếp vào biến lưu trữ cục bộ toàn cầu. Làm cho nó dễ dàng để đặt giả trong trình bao bọc cho các bài kiểm tra.


2
    describe('getToken', () => {
    const Auth = new AuthService();
    const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Ik1yIEpvc2VwaCIsImlkIjoiNWQwYjk1Mzg2NTVhOTQ0ZjA0NjE5ZTA5IiwiZW1haWwiOiJ0cmV2X2pvc0Bob3RtYWlsLmNvbSIsInByb2ZpbGVVc2VybmFtZSI6Ii9tcmpvc2VwaCIsInByb2ZpbGVJbWFnZSI6Ii9Eb3Nlbi10LUdpci1sb29rLWN1dGUtbnVrZWNhdDMxNnMtMzExNzAwNDYtMTI4MC04MDAuanBnIiwiaWF0IjoxNTYyMzE4NDA0LCJleHAiOjE1OTM4NzYwMDR9.YwU15SqHMh1nO51eSa0YsOK-YLlaCx6ijceOKhZfQZc';
    beforeEach(() => {
        global.localStorage = jest.fn().mockImplementation(() => {
            return {
                getItem: jest.fn().mockReturnValue(token)
            }
        });
    });
    it('should get the token from localStorage', () => {

        const result  = Auth.getToken();
        expect(result).toEqual(token);

    });
});

Tạo một bản giả và thêm nó vào globalobjectt


2

Bạn có thể sử dụng phương pháp này, để tránh chế nhạo.

Storage.prototype.getItem = jest.fn(() => expectedPayload);

2

Bạn cần mô phỏng bộ nhớ cục bộ bằng đoạn mã này

// localStorage.js

var localStorageMock = (function() {
    var store = {};

    return {
        getItem: function(key) {
            return store[key] || null;
        },
        setItem: function(key, value) {
            store[key] = value.toString();
        },
        clear: function() {
            store = {};
        }
    };

})();

Object.defineProperty(window, 'localStorage', {
     value: localStorageMock
});

Và trong cấu hình jest:

"setupFiles":["localStorage.js"]

Hãy hỏi bất cứ điều gì .


1

Giải pháp sau đây tương thích để thử nghiệm với TypeScript, ESLint, TSLint và Prettier config { "proseWrap": "always", "semi": false, "singleQuote": true, "trailingComma": "es5" }:

class LocalStorageMock {
  public store: {
    [key: string]: string
  }
  constructor() {
    this.store = {}
  }

  public clear() {
    this.store = {}
  }

  public getItem(key: string) {
    return this.store[key] || undefined
  }

  public setItem(key: string, value: string) {
    this.store[key] = value.toString()
  }

  public removeItem(key: string) {
    delete this.store[key]
  }
}
/* tslint:disable-next-line:no-any */
;(global as any).localStorage = new LocalStorageMock()

HT / https://stackoverflow.com/a/51583401/101290 để biết cách cập nhật global.localStorage


1

Để làm tương tự trong Bản đánh máy, hãy làm như sau:

Thiết lập một tệp với các nội dung sau:

let localStorageMock = (function() {
  let store = new Map()
  return {

    getItem(key: string):string {
      return store.get(key);
    },

    setItem: function(key: string, value: string) {
      store.set(key, value);
    },

    clear: function() {
      store = new Map();
    },

    removeItem: function(key: string) {
        store.delete(key)
    }
  };
})();
Object.defineProperty(window, 'localStorage', { value: localStorageMock });

Sau đó, bạn thêm dòng sau vào gói.json theo cấu hình Jest của bạn

"setupTestFrameworkScriptFile":"PATH_TO_YOUR_FILE",

Hoặc bạn nhập tệp này trong trường hợp thử nghiệm của bạn, nơi bạn muốn giả định bộ lưu trữ cục bộ.


0

Điều này làm việc cho tôi,

delete global.localStorage;
global.localStorage = {
getItem: () => 
 }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.