-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove AsyncStorage dependency #240
Conversation
Should we create an issue for this, @marcaaron? |
jestSetup.js
Outdated
jest.mock('./lib/storage/providers/SQLiteStorage', () => require('./lib/storage/providers/__mocks__/SQLiteStorage')); | ||
jest.mock('./lib/storage/NativeStorage', () => require('./lib/storage/__mocks__/NativeStorage')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're using __mocks__/localforage.js
for both Native and Web - I endorse this, but let's just call it Storage mock
Then we can:
- move
__mocks__/localforage.js
tolibs/storage/__mocks__/index.js
- adjust the code to make it match the storage provider interface
- remove any other storage mocks
Because the specific storage implementation does not really matter - they all have the same interface
I remember @marcaaron came up with a mock for sqlite that can be used to validate actual syntax or possible sqlite errors (because the mock executes sqlite under the hood)
When desired (specific test suite) it can be put in place, while the default can be to always use the generic storage mock (after all we're don't have any other mocks right now but localforage mock)
For example a good place to use sqlite mock is if we write tests for the provider
tests/unit/storage/providers/SQLiteStorageTest.js
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with all of the above.
When desired (specific test suite) it can be put in place, while the default can be to always use the generic storage mock (after all we're don't have any other mocks right now but localforage mock)
Agree and we can always come back later to do it. And who knows, maybe one day we will see SQLite in the browser?
tests/unit/onyxCacheTest.js
Outdated
@@ -400,8 +400,8 @@ describe('Onyx', () => { | |||
|
|||
describe('Onyx with Cache', () => { | |||
let Onyx; | |||
let LocalForageMock; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's just call this Storage
or StorageMock
- specific storage provider (or mock) shouldn't matter in tests - a feature should work the same regardless of the underlying storage provider (as long as interface match)
This means that we don't have to write tests for every storage provider - but use one generic storage mock
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically if we have mocks for each storage provider we can re-run the test suite with another storage provider mock
But usually this has no benefit, because in either case it's just a mock - not the real thing
A test does not depend on a storage provider (unless it tests the actual provider), but on the assumption that if storage works "correctly" it results in the expected result
getItem
returns a Promise with an itemsetItem
returns a Promise
the test is about whether we use the result of getItem
correctly
the test is whether we call setItem
with expected parameters
the specific storage provider doesn't matter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would also make sense, to remove the LocalForageProviderTest.js
tests, since we're not really testing localForage
, only the mock
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would also make sense, to remove the
LocalForageProviderTest.js
tests, since we're not really testinglocalForage
, only the mock
Not exactly - the test verifies we're using the underlying library localforage
as intended
For example a test verifies that if we call StorageProvider.multiSet(pairs)
then for each pair we call localforage.setItem
with the expected key
and value
So the test is would multiSet
call setItem
correctly for each pair - we don't have to use the real localforage library to test that
2e52c5a
to
c50ef55
Compare
These changes look good to me. Like that we are cleaning it up. @Gonals is right that maybe we should create an issue for it. I'll do it now and update the description. |
Still need to fix some tests here... I'll let you know when those are done @kidroca @marcaaron Should be done by tomorrow hopefully |
@marcaaron @kidroca I now got all tests except for 2 running. Could one of you maybe have a look at the remaining 2 tests? Somehow, "onyxCacheTest.js > Should clean cache when connections to eviction keys happen" sometimes fails, and sometimes not 🤔 If you run the test by itself, it succeeds "withOnyxTest > should update withOnyx subscriber multiple times when merge is used" also fails atm, i can't figure out why.. thank you in advance! |
Hmm I would get something is wrong with the mock resolved value. Maybe the mock is getting reset at an unexpected time. When I debug the test suite I can see the value is not resolved as expected and we get react-native-onyx/tests/unit/onyxCacheTest.js Lines 417 to 442 in 7ce6ae4
|
yes, confirmed |
Oh I see it now haha. We are clearing them when calling |
try this diff --git a/tests/unit/onyxCacheTest.js b/tests/unit/onyxCacheTest.js
index bd2bf54..9cce84d 100644
--- a/tests/unit/onyxCacheTest.js
+++ b/tests/unit/onyxCacheTest.js
@@ -613,16 +613,16 @@ describe('Onyx', () => {
});
it('Should clean cache when connections to eviction keys happen', () => {
- // Given storage with some data
- StorageMock.getItem.mockResolvedValue('"mockValue"');
- const range = _.range(1, 10);
- StorageMock.getAllKeys.mockResolvedValue(_.map(range, n => `key${n}`));
-
jest.useFakeTimers();
// Given Onyx with LRU size of 3
return initOnyx({maxCachedKeysCount: 3})
.then(() => {
+ // Given storage with some data
+ StorageMock.getItem.mockResolvedValue('"mockValue"');
+ const range = _.range(1, 10);
+ StorageMock.getAllKeys.mockResolvedValue(_.map(range, n => `key${n}`));
+
// When 4 connections for different keys happen
Onyx.connect({key: 'key1', callback: jest.fn()});
Onyx.connect({key: 'key2', callback: jest.fn()}); |
Oh hmm not quite actually, sorry. Definitely Onyx library is using the old |
Hey, I'll take a look tonight as well |
This was a hard one One of the problems is the sync queue used in the mockI guess because it only resolves one promise at a time it does not integrate correctly with our Let's not have that in the mock, as a rule of thumb mocks should be as dumb as possible
The other thing is with the new mock
So when we run Here's a small diff that fixes Index: lib/storage/__mocks__/index.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/lib/storage/__mocks__/index.js b/lib/storage/__mocks__/index.js
--- a/lib/storage/__mocks__/index.js (revision 7ce6ae45ea42bad394014f2b855ffc010dff5a1c)
+++ b/lib/storage/__mocks__/index.js (date 1680287765397)
@@ -24,7 +24,8 @@
const localForageMock = {
setItem(key, value) {
- return setItemQueue.push({key, value});
+ storageMapInternal[key] = value;
+ return Promise.resolve();
},
multiSet(pairs) {
const setPromises = _.map(pairs, ([key, value]) => this.setItem(key, value));
Index: tests/unit/onyxCacheTest.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/tests/unit/onyxCacheTest.js b/tests/unit/onyxCacheTest.js
--- a/tests/unit/onyxCacheTest.js (revision 7ce6ae45ea42bad394014f2b855ffc010dff5a1c)
+++ b/tests/unit/onyxCacheTest.js (date 1680287882477)
@@ -414,13 +414,18 @@
},
};
- function initOnyx(overrides) {
+ // Initialize clean modules before each test
+ // This reset top level static variables (in Onyx.js, OnyxCache.js, etc.)
+ beforeEach(() => {
+ jest.resetModules();
const OnyxModule = require('../../lib');
Onyx = OnyxModule.default;
withOnyx = OnyxModule.withOnyx;
StorageMock = require('../../lib/storage').default;
cache = require('../../lib/OnyxCache').default;
+ });
+ function initOnyx(overrides) {
Onyx.init({
keys: ONYX_KEYS,
safeEvictionKeys: [ONYX_KEYS.COLLECTION.MOCK_COLLECTION],
@@ -435,12 +440,6 @@
.then(() => jest.clearAllMocks());
}
- // Always use a "fresh" instance
- beforeEach(() => {
- jest.resetModules();
- return initOnyx();
- });
-
it('Expect a single call to getItem when multiple components use the same key', () => {
// Given a component connected to Onyx
const TestComponentWithOnyx = withOnyx({ I see you've also added/modified some tests and now they implicitly depend on the mock having a sync queue I'll check why the other test is failing next week |
Actually here's the fix for the 2nd one It's seems there's some promise we need to wait before starting calling Index: tests/unit/withOnyxTest.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/tests/unit/withOnyxTest.js b/tests/unit/withOnyxTest.js
--- a/tests/unit/withOnyxTest.js (revision 7ce6ae45ea42bad394014f2b855ffc010dff5a1c)
+++ b/tests/unit/withOnyxTest.js (date 1680290315506)
@@ -1,5 +1,5 @@
import React from 'react';
-import {render} from '@testing-library/react-native';
+import {act, render} from '@testing-library/react-native';
import Onyx, {withOnyx} from '../../lib';
import ViewWithText from '../components/ViewWithText';
import ViewWithCollections from '../components/ViewWithCollections';
@@ -52,10 +52,13 @@
const onRender = jest.fn();
render(<TestComponentWithOnyx onRender={onRender} />);
- Onyx.merge(`${ONYX_KEYS.COLLECTION.TEST_KEY}1`, {ID: 123});
- Onyx.merge(`${ONYX_KEYS.COLLECTION.TEST_KEY}2`, {ID: 234});
- Onyx.merge(`${ONYX_KEYS.COLLECTION.TEST_KEY}3`, {ID: 345});
- return waitForPromisesToResolve()
+ return waitForPromisesToResolve()
+ .then(() => {
+ Onyx.merge(`${ONYX_KEYS.COLLECTION.TEST_KEY}1`, {ID: 123});
+ Onyx.merge(`${ONYX_KEYS.COLLECTION.TEST_KEY}2`, {ID: 234});
+ Onyx.merge(`${ONYX_KEYS.COLLECTION.TEST_KEY}3`, {ID: 345});
+ })
+ .then(waitForPromisesToResolve)
.then(() => {
expect(onRender).toHaveBeenCalledTimes(4);
}); |
damn! nice work @kidroca, you are a true JS 🕵️♂️ |
Thanks a lot @kidroca and @marcaaron . This bug almost killed me 😅 |
@kidroca did i get you correctly, that you suggest removing the sync queue from the mock completely? Would agree with that, makes sense that there should be no unnecessary overhead in the mock |
Which tests do you mean? This one is the only one i find that somehow depends on setItemQueue? https://github.com/chrispader/react-native-onyx/blob/remove-async-storage/tests/unit/storage/providers/LocalForageProviderTest.js#L106 |
Also i have changed a few things in the storage mock, so maybe this is already obsolete |
Should be ready for review from my side! All tests are passing 🚀 Thanks a lot @kidroca @marcaaron |
Once this one is merged, we should test the changes in the JSON_PATCH PR.. |
@kidroca @marcaaron
Details
This PR removes all remaining dependencies to
@react-native-async-storage/async-storage
and re-writes tests so they useLocalForage
mock instead.Related Issues
$ Expensify/App#16455
Came up in the discussion here: #238 (comment)