skip to Main Content

I am running node version 16.15.0 and have the package.json dependencies:

"jest": "^28.1.0",
"mongodb-memory-server": "^8.6.0",
"mongoose": "^6.3.5",

I have a mongoose.Schema set up in a User.js module:

import mongoose from 'mongoose'
import validator from 'validator'
import bcrypt from 'bcryptjs'
import jwt from 'jsonwebtoken'

const UserSchema = new mongoose.Schema({
  name: {
    type: String,
    required: [true, 'Please provide name'],
    minlength: 3,
    maxlength: 20,
    trim: true,
  },
  email: {
    type: String,
    required: [true, 'Please provide email'],
    validate: {
      validator: validator.isEmail,
      message: 'Please provide a valid email',
    },
    unique: true,
  },
  password: {
    type: String,
    required: [true, 'Please provide password'],
    minlength: 6,
    select: false,
  },
  role: {
    type: String,
    trim: true,
    maxlength: 20,
    default: 'PLAYER',
  },
})

UserSchema.pre('save', async function () {
  // console.log(this.modifiedPaths())
  if (!this.isModified('password')) return
  const salt = await bcrypt.genSalt(10)
  this.password = await bcrypt.hash(this.password, salt)
})

UserSchema.methods.createJWT = function () {
  return jwt.sign({ userId: this._id }, process.env.JWT_SECRET, {
    expiresIn: process.env.JWT_LIFETIME,
  })
}

UserSchema.methods.comparePassword = async function (candidatePassword) {
  const isMatch = await bcrypt.compare(candidatePassword, this.password)
  return isMatch
}

export default mongoose.model('User', UserSchema)

(note the UserSchema.methods.createJWT = function, because this is in the test below)

and finally a simple Jest test (I am only just starting out with Jest):

import mongoose from 'mongoose'
import dotenv from 'dotenv'
import { MongoMemoryServer } from 'mongodb-memory-server'
import User from '../../models/User.js'

describe('User Schema suite', () => {
  dotenv.config()

  const env = process.env

  var con, mongoServer

  beforeAll(async () => {
    mongoServer = await MongoMemoryServer.create()
    con = await mongoose.connect(mongoServer.getUri(), {})

    jest.resetModules()
    process.env = { ...env }
  })

  afterAll(async () => {
    if (con) {
      con.disconnect()
    }
    if (mongoServer) {
      await mongoServer.stop()
    }

    process.env = env
  })

  test('should read the environment vars', () => {
    expect(process.env.JWT_SECRET).toBeTruthy()
    expect(process.env.JWT_SECRET).toEqual('?E(H+MbQeThWmYq3t6w9z$C&F)J@NcRf')

    expect(process.env.JWT_LIFETIME).toBeTruthy()
    expect(process.env.JWT_LIFETIME).toEqual('1d')
  })

  test('should create and sign a good token', async () => {
    const user = await User.create({
      name: 'Mike',
      email: '[email protected]',
      password: 'secret',
    })

    expect(user.createJWT()).toBeTruthy()
  })
})

BTW: I also tried to add the _id in manually with this User.create expression

    const user = await User.create({
      _id: '62991d39873ec2778e34f114',
      name: 'Mike',
      email: '[email protected]',
      password: 'secret',
    })

But it made no difference.

The first test passes, however the second one fails with the following error:

mike@mike verser % npm test

> [email protected] test
> jest --testEnvironment=node --runInBand ./tests

 FAIL  tests/models/User.test.js
  ● User Schema suite › should create and sign a good token

    TypeError: Cannot read properties of null (reading 'ObjectId')

      at Object.<anonymous> (node_modules/mongoose/lib/types/objectid.js:13:44)
      at Object.<anonymous> (node_modules/mongoose/lib/utils.js:9:18)


Test Suites: 1 failed, 1 skipped, 1 of 2 total
Tests:       1 failed, 1 skipped, 1 passed, 3 total
Snapshots:   0 total
Time:        1.127 s, estimated 2 s
Ran all test suites matching /./tests/i.

The code in User.js works in production where I am using Mongo Community v5.0.7 (in a docker container).

So why can’t I access the _id value when I am using MongoMemoryServer instead? Is there something that I need to set? Or is there something else that I’m doing wrong?

2

Answers


  1. Chosen as BEST ANSWER

    So, ultimately, the problem was that Jest couldn't handle the various arrow functions and imports I was making in my ES6 project. After going around at least a hundred different answers, involving babel or adding arguments to the Jest script call in package.json, and having none of them work except in really simple test cases...i gave up on Jest and have gone with Mocha. I'm getting a lot further now.

    In short: if you're using ES6 - avoid Jest.

    I'm told though, that there are various tools that can get TypeScript working with Jest, but I wasn't about to rewrite my whole project.


  2. I was able to replicate it, it came from jest.config.js

    I changed it from

    /** @type {import('ts-jest').JestConfigWithTsJest} */
    module.exports = {
      preset: 'ts-jest',
      testEnvironment: 'node',
      moduleFileExtensions: ['js', 'json', 'ts'],
      rootDir: 'src',
      testRegex: '.*\.spec\.ts$',
      collectCoverageFrom: ['**/*.(t|j)s'],
      collectCoverage: true,
      coverageDirectory: '../coverage',
      clearMocks: true,
      resetMocks: true,
      resetModules: true,
    };
    

    to

    /** @type {import('ts-jest').JestConfigWithTsJest} */
    module.exports = {
      preset: 'ts-jest',
      testEnvironment: 'node',
      moduleFileExtensions: ['js', 'json', 'ts'],
      rootDir: 'src',
      testRegex: '.*\.spec\.ts$',
      collectCoverageFrom: ['**/*.(t|j)s'],
      collectCoverage: true,
      coverageDirectory: '../coverage'
    };
    

    And the error is gone.

    You can give it a try using this test

    import { Connection, connect, Schema } from 'mongoose';
    import { MongoMemoryServer } from 'mongodb-memory-server';
    
    describe('TagController Unit tests', () => {
      let mongod: MongoMemoryServer;
      let mongoConnection: Connection;
    
      beforeEach(async () => {
        mongod = await MongoMemoryServer.create();
        const uri = mongod.getUri();
        mongoConnection = (await connect(uri)).connection;
        mongoConnection.model('SchemaTest', new Schema({ test: String }));
      });
    
      afterAll(async () => {
        await mongoConnection.dropDatabase();
        await mongoConnection.close();
        await mongod.stop();
      });
    
      afterEach(async () => {
        const { collections } = mongoConnection;
        // eslint-disable-next-line no-restricted-syntax, guard-for-in
        for (const key in collections) {
          const collection = collections[key];
          // eslint-disable-next-line no-await-in-loop
          await collection.deleteMany({});
        }
      });
    
      it('Create new Tag', async () => {
        expect({}).toBeDefined();
      });
    });
    

    if you use this jest configuration.

    clearMocks: true,
    resetMocks: true,
    resetModules: true,
    

    You will get TypeError: Cannot read properties of null (reading ‘ObjectId’)
    For this example I used "jest": "^29.2.1"

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search