function registerHooks(expThisType /* QUERY, DOC, UNION, NEVER  */, method /* save, updateOne etc. */, options/* ? {document,query} */) {

      for (const hook of ['pre', 'post']) {
        // only for TSD test generation ----------------------
        if (GEN_TSD) {
          const thisTypeStr = ['Query<any, any>', 'HydratedDocument<IDocument>', 'Query<any, any>|HydratedDocument<IDocument>', 'never'][expThisType];
          if (hook === 'pre') {
            tsdOut.push(`schema.${hook}(${jsfy(method).replace(/"/g, '')}${options ? ', ' + jsfy(options) : ''}, function() {
    expectType<${thisTypeStr}>(this);
});`);
          } else if (hook === 'post') {
            tsdOut.push(`schema.${hook}(${jsfy(method).replace(/"/g, '')}${options ? ', ' + jsfy(options) : ''}, function(res) {
    expectType<${thisTypeStr}>(this);
    expectNotType<Query<any, any>>(res);
});`);
          }
        }
        // --------------------------------------------------

        const methods = method instanceof Array ? method : [method];
        // create signature (for messages)
        const hookSignature = [ // not 100% accurate, but good enough for this test
          () => `${hook}<T = Query<any, any>>(method: '${methods.join('\'|\'')}'${options ? ', ' + JSON.stringify(options) : ''}, fn: ${hook[0].toUpperCase()}${hook.slice(1)}MiddlewareFunction<T>): this;`,
          () => `${hook}<T = HydratedDocument<DocType, TInstanceMethods>>(method: '${methods.join('\'|\'')}'${options ? ', ' + JSON.stringify(options) : ''}, fn: ${hook[0].toUpperCase()}${hook.slice(1)}MiddlewareFunction<T>): this;`,
          () => `${hook}<T = HydratedDocument<DocType, TInstanceMethods>|Query<any, any>>(method: '${methods.join('\'|\'')}'${options ? ', ' + JSON.stringify(options) : ''}, fn: ${hook[0].toUpperCase()}${hook.slice(1)}MiddlewareFunction<T>): this;`,
          () => `${hook}<T = never>(method: '${methods.join('\'|\'')}'${options ? ', ' + JSON.stringify(options) : ''}, fn: ${hook[0].toUpperCase()}${hook.slice(1)}MiddlewareFunction<T>): this;`
        ][expThisType]();
        assert(!signatures.has(hookSignature), `hook already registered: ${hookSignature}`);
        signatures.set(hookSignature, new Set());

        // the callback checking the type and registering the call
        const fn = function() {
          const actThisType = getTypeName(this);

          switch (expThisType) {
            case QUERY:
            case DOC:
              assert(actThisType === TYPE_TO_NAME[expThisType], `this was ${actThisType}, should be ${TYPE_TO_NAME[expThisType]} for hook ${hookSignature}`); break;
            case UNION: assert(actThisType === 'Document' || actThisType === 'Query', `this was ${actThisType}, should be ${TYPE_TO_NAME[expThisType]} for hook ${hookSignature}`); break;
            case NEVER: fail(`this was ${actThisType}, hook ${hookSignature} should never have been called`); break;
          }
          const calledWith = signatures.get(hookSignature);
          calledWith.add(actThisType);

        };

        const fnPost = function(res) {
          fn.call(this);
          const resType = getTypeName(res);
          assert(resType !== 'Query', 'type of res in post middleware is not expected to be a Query');
        };

        // register the hook
        if (options) {
          switch (hook) {
            case 'pre': schema.pre(method, options, fn); break;
            case 'post': schema.post(method, options, fnPost); break;
          }
        } else {
          switch (hook) {
            case 'pre': schema.pre(method, fn); break;
            case 'post': schema.post(method, fnPost); break;
          }
        }
      }
    }
One single test for all the different types of hooks. This test is checking the type annotations of hooks in index.d.ts.