Skip to content

Instantly share code, notes, and snippets.

@hyb175
Last active August 13, 2023 17:41
Show Gist options
  • Star 34 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save hyb175/beb9ceed4c34300ba7c77d3d6d44ae52 to your computer and use it in GitHub Desktop.
Save hyb175/beb9ceed4c34300ba7c77d3d6d44ae52 to your computer and use it in GitHub Desktop.
Realm Mock for jest
// https://github.com/realm/realm-js/issues/370#issuecomment-270849466
export default class Realm {
constructor(params) {
this.schema = {};
this.callbackList = [];
this.data = {};
this.schemaCallbackList = {};
params.schema.forEach((schema) => {
this.data[schema.name] = {};
});
params.schema.forEach((schema) => {
this.schema[schema.name] = schema;
});
this.lastLookedUpModel = null;
}
objects(schemaName) {
this.lastLookedUpModel = schemaName;
const objects = Object.values(this.data[schemaName]);
objects.values = () => objects;
objects.sorted = () => this.compareFunc ? objects.sort(this.compareFunc) : objects.sort();
objects.addListener = (cb) => {
if (this.schemaCallbackList[schemaName]) {
this.schemaCallbackList[schemaName].push(cb);
} else {
this.schemaCallbackList[schemaName] = [cb];
}
};
objects.removeListener = () => {};
objects.filtered = this.filtered ? this.filtered.bind(this, schemaName) : () => objects;
return objects;
}
write(fn) {
this.writing = true;
fn();
this.writing = false;
}
create(schemaName, object) {
const modelObject = object;
const properties = this.schema[schemaName].schema.properties;
Object.keys(properties).forEach((key) => {
if (modelObject[key] && modelObject[key].model) {
this.data[modelObject[key].model][modelObject[key].id] = this.create(
modelObject[key].model, modelObject[key],
);
} else if (modelObject[key] && modelObject[key].length && modelObject[key][0].model) {
modelObject[key].forEach((obj) => {
this.data[modelObject[key][0].model][obj.id] = obj;
});
modelObject[key].filtered = this.filtered ? this.filtered : () => modelObject[key];
modelObject[key].sorted = () => modelObject[key].sort();
} else if (modelObject[key] === undefined) {
if (typeof properties[key] === 'object' && properties[key].optional) {
modelObject[key] = null;
}
if (typeof properties[key] === 'object' && ['list', 'linkingObjects'].includes(properties[key].type)) {
modelObject[key] = [];
modelObject[key].filtered = () => [];
modelObject[key].sorted = () => [];
}
}
});
this.data[schemaName][modelObject.id] = modelObject;
if (this.writing) {
if (this.schemaCallbackList[schemaName]) {
this.schemaCallbackList[schemaName].forEach(cb => cb(schemaName, {
insertions: { length: 1 },
modifications: { length: 0 },
deletions: { length: 0 },
}));
}
this.callbackList.forEach((cb) => { cb(); });
}
return modelObject;
}
objectForPrimaryKey(model, id) {
this.lastLookedUpModel = model;
return this.data[model][id];
}
delete(object) {
if (this.lastLookedUpModel || object.model) {
const model = object.model ? object.model : this.lastLookedUpModel
if (Array.isArray(object)) {
object.forEach((item) => {
delete this.data[model][item.id];
});
}
delete this.data[model][object.id];
if (this.writing) {
if (this.schemaCallbackList[model]) {
this.schemaCallbackList[model].forEach(cb => cb(model, {
insertions: { length: 0 },
modifications: { length: 0 },
deletions: { length: 1 },
}));
}
this.callbackList.forEach((cb) => { cb(); });
}
}
}
deleteAll() {
Object.keys(this.schema).forEach((key) => {
if (this.writing && this.schemaCallbackList[this.schema[key].name]) {
this.schemaCallbackList[this.schema[key].name].forEach(cb => cb(key, {
insertions: { length: 0 },
modifications: { length: 0 },
deletions: { length: Object.values(this.data[this.schema[key].name]).length },
}));
}
this.data[this.schema[key].name] = {};
});
if (this.writing) this.callbackList.forEach((cb) => { cb(); });
}
addListener(event, callback) {
this.callbackList.push(callback);
}
prepareData(schemaName, objects) {
objects.forEach((object) => {
this.create(schemaName, object);
});
}
}
Realm.Object = class Object {
isValid() { return true; }
};
@jdelafon
Copy link

Thanks for sharing this.
32: const properties = this.schema[schemaName].schema.properties;
should be
32: const properties = this.schema[schemaName].properties;

@josenaves
Copy link

@hyb175 How do you use this mock? Can you help me with that ?

@hyb175
Copy link
Author

hyb175 commented Jun 1, 2018

Update log:
Today's update mainly adds better support for realm listeners.

@hyb175
Copy link
Author

hyb175 commented Jun 1, 2018

@josenaves Hi, follow the instructions here https://facebook.github.io/jest/docs/en/manual-mocks.html and drop this file in your test root in a folder called __mock__. Name this file as realm.js so any import of realm will be mocked with this file.

@maccomaccomaccomacco
Copy link

@hyb175 do you fill the this.data object with some properties?
I placed the mock in the proper folder but I don't know if it's working, I mean...I don't think it's working

@hyb175
Copy link
Author

hyb175 commented Jan 20, 2019

@marcoautiero Sorry for the late reply, I haven't checked this for a while. So the way to populate data for test is with prepareData.
In setup of test, I would call realm.prepareData() to insert mocked data into this mockedRealm.

@tuomohopia
Copy link

This seems like an impressively detailed mock but I can't get it to work.

Just keep hitting:

TypeError: _realm.default.open is not a function

I tried adding an open() method. I think the problem is with the export default somehow. Anyone got this working anyhow?

@hyb175
Copy link
Author

hyb175 commented May 22, 2019

@tuomohopia Hi, the way you want to mock open() method is gonna be similar to how Object is mocked.
Something like the following should work:

Realm.open = (params) => {
  return new Promise((resolve) => {
    resolve(new Realm(params));
  });
}

@rsbh
Copy link

rsbh commented Mar 21, 2020

how to use filtered and sort?

@rafaelcavalcante
Copy link

how to use filtered and sort?

I'm wondering the same. Hope an anwser shows up :(

@hyb175
Copy link
Author

hyb175 commented Jul 30, 2020

@rsbh @rafaelcavalcante
Sorry for the late reply. Filtered and sort are definitely the more tricky ones. I have put together a very hacky way to solve this, so I can't say I recommend doing this 😂
See the following for an example:

  realm.filtered = jest.fn().mockImplementation(function mockedFiltered() {
      const result = realm.objects('Car');
      if (result.length) {
        result.filtered = jest.fn((query, leadingChar) => {
          if (leadingChar === 'D') {
            result.sorted = () => cars; // cars is pre-defined earlier
          } else {
            result.sorted = () => [];
          }
          return result;
        });
      }
      return result;
    });

The example should support calls like realm.objects('Car').filtered('name BEGINSWITH $0', 'D');
This way, we are mocking out the filtered method and sort can follow a similar pattern.

@c-goettert
Copy link

Thanks for this mock, it helped me alot as part of my business logic is wired up with realm interactions.
In my case I had to adjust the following lines to make it work completely:

const properties = this.schema[schemaName].schema.properties;
to
const properties = this.schema[schemaName].properties;

also, since realm returns proxy objects containing a toJSON function, I added:

const modelObjectJSON = { ...modelObject }
modelObject.toJSON = () => modelObjectJSON
return modelObject

@hyb175
Copy link
Author

hyb175 commented Feb 22, 2022

@c-goettert Thanks for the addition!

@aavelozo
Copy link

aavelozo commented Oct 19, 2022

esse código me ajudou muito, gostaria de contribuir com um MVP de uma função filtered. Neste MVP usei o tradicional eval do javascript para "evoluir" a expressão contida no filtered, apenas substituindo o nome do campo pelo seu respectivo valor, assim a evolução da expressão como um todo deve atender. Temos que tratar bastante coisa ainda (como aspas simples e duplas por exemplo), mas já funciona para alguns casos, consegui utilizar no meu teste de login:

Substituir a linha 30 do código original por isto:

  objects.filtered = (filters) => {
    if (filters) {
        let filteredResult = [];
        let keys = Object.keys(objects.values()[0] ||[]);
        let filterTemp = filters;
        let valuesTemp = objects.values();
        for(let ind in valuesTemp) {
            filterTemp = filters;
            for(let key in keys) {
                filterTemp = filterTemp.replace(keys[key], "'" + valuesTemp[ind][keys[key]] + "'");  
            }
            filterTemp = filterTemp.replace(/ and /gi," && ");
            filterTemp = filterTemp.replace(/ or /gi," || ");
            //console.log("filterTemp",filters, filterTemp, eval(filterTemp));
            if (eval(filterTemp)) {
                filteredResult.push(valuesTemp[ind]);
            }
        }
        return filteredResult;
    } else {
        return objects;
    }
  } 

@zachnicoll
Copy link

Also needs a method for close, for those that use it.

  close() {
    return true;
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment