mercredi 18 mars 2020

How to test a small function that relies heavily database interactions?

I am creating a series of routes to build a quiz of sorts. I've decided to build it pretty much like a linked list with each element in the form pointing to the next/prev elements. I want everything to have tests, but I can't figure out how to write a test for the below:

    async create(next){

    // check if form already has elements added to it
    const elements = await db.elements.findAll({ where: { form_id: this.formId } });

    // if form already has elements added and next is not provided, append element to the end of the form
    if (elements.length && !next) {
        await this._createAtEnd(elements, next);
    }
    // if next is not the end of the element list, insert element before next element
    else if (elements.length){
        await this._insert(elements, next);
    }

    return db.elements.findAll({ where: { form_id: this.formId } });
  }

  async _insert(elements, next){
      const tempPrevious = elements.filter(el => el.next_element_id === next).pop();
      const tempNext = tempPrevious ? elements.filter(el => el.id === tempPrevious.next_element_id).pop() : null;


      const created =  await db.elements.create({
          form_id: this.formId,
          previous_element_id: tempPrevious ? tempPrevious.id : null,
          next_element_id: next,
      });


      // update the element prior to the newly inserted element
      if (tempPrevious) {
          return Promise.all( [
              tempPrevious.update({ next_element_id: created.id }),
              tempNext.update({ previous_element_id: created.id }),
          ]);
      }

      // element is being inserted at the beginning
      const first =  elements.map(el => el.previous_element_id === null).pop();
      return first.update({ previous_element_id: created.id });
  }

Let's say I stub out the database (db) so that the test looks like the following:

 it('should create a new element', async () => {
    const formId = 1;
    const next = 2;
    const data = [
      { id: 0, form_id: formId, previous_element_id: null, next_element_id: 1, },
      { id: 1, form_id: formId, previous_element_id: 0, next_element_id: 2, },
      { id: 2, form_id: formId, previous_element_id: 1, next_element_id: null, },
    ];
    data.forEach(el => el.update = function(args){fakeDb.update(this, args)})

    const fakeDb = {
      findAll: async (args) => {
        if (args.where) {
          const keys = Object.keys(args.where);
          return data.filter(el => {
            return keys.filter(key => el[key] === args.where[key]).length;
          });
        }
        return data;
      },
      create: async (item) => {
        item.id = data[data.length - 1].id + 1;
        data.push(item);
        return item;
      },
      update: (obj, args) => {
        const keys = Object.keys(args);
        for (let i=0; i< data.length; i++){
          if (data[i].id === obj.id) {
            keys.forEach(key => data[i][key] = args[key]);
            break;
          }
        }
      },
    };
    sinon.stub(db.elements, 'create').callsFake(fakeDb.create);
    sinon.stub(db.elements, 'findAll').callsFake(fakeDb.findAll);

    const newElement = new NewElement({ formId });
    const created = await newElement.create(next);

    expect...

Looking at this I feel like I'm writing way to much code for fakeDb and not actually testing create. My initial feeling was to return a constant value, like:

const fakeDb = {
      create: Promise.resolve({ id: 1, forms_id: formId, previous_element_id: 0, next_element_id: 2 }),
};

However, then I really felt like the test was useless? The function would still be able to pass regardless of what I did internally. Is it customary in testing to create a data set you can manipulate, even if you're then creating a bunch of methods to emulate the db?

I feel good that I could take the fakeDb obj I wrote out of this test and create some kind of helper function so I'm not rewriting it in each test. I also don't want to overly complicate the whole thing either.

Aucun commentaire:

Enregistrer un commentaire