'Why does batching with a dataloader not work in a test?

Problem

I am trying to test the performance of the following query:

query {
  classes {
    teachers {
      user_id
    }
  }
}

When I start up my server and run the query through the graphQL playground, the dataloader works as expected and batches the queries: teachersForClasses only runs once.

When I run the test to benchmark the query's performance, teachersForClasses runs once for each unique class returned by getClasses.

Why do the behaviours differ? In both cases an HTTP request is being sent to an already running server.

Background

I am using the dataloader library for node.js on a project where I create a graphQL API with Apollo Server.

Code

function getClasses(args, ctx) {
  /* returns a list of Class objects */
}

const resolvers = {
  Class: {
    teachers: (parent: Class, _args, ctx: Context) => 
      ctx.loaders.class.teachers.load(parent.class_id)
  }
  Query: {
    class: (_parent, args, ctx): Promise<Class[]> => getClasses(args, ctx)
  }
}

The loader is defined like this:

function teachersForClasses(classIds: readonly string[]) {
  console.log(classIds) // added for debugging
  /* returns an array of User objects for each class ID */
}

export const loader = {
  class: {
    teachers: new Dataloader<string, User[]>>(teachersForClasses)
  }
}

Tests

A server is already running at http://localhost:8080 before this runs.

const url = 'http://localhost:8080'
const request = supertest(url)

async function runQuery(token: string) {
  return request
    .post('/user')
    .set({
      ContentType: 'application/json',
      Authorization: token
    })
    .send({
      query: `
      {
        classes {
          teachers {
            user_id
          }
        }
      }`
    })
}

describe('benchmarks', () => {
  it('getTeachersForClasses', () => {
    /*populates the database with data*/

    ...

    for (let i=0; i < 10; i++) {
      console.time('query')
      const start = Date.now()
      await runQuery(userToken)
      const end = Date.now()
      console.timeEnd('query')
    }
  })
})


Solution 1:[1]

I found the problem: there was an async directive (permission check) for 'teachers' which meant that each time the dataloader was called with .load(), it was part of a different event loop 'tick' (explained here) & thus the calls weren't batched as expected.

This wasn't an issue in the graphQL playground because I was making the call with an admin user, so the directive resolved immediately.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 raphael-p