diff --git a/.travis.yml b/.travis.yml
index ddcb0f18..24395319 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,17 +1,18 @@
 language: node_js
 
 node_js:
-  - "6"
-  - "8"
+  - "12"
   - "10"
+  - "8"
 
 services:
   - docker
 
 env:
-  - MONGODB_VERSION="2.6"
-  - MONGODB_VERSION="3.6"
+  - MONGODB_VERSION="4.2"
   - MONGODB_VERSION="4.0"
+  - MONGODB_VERSION="3.6"
+  - MONGODB_VERSION="3.4"
 
 before_install:
   - docker run -d -p 127.0.0.1:27017:27017 mongo:$MONGODB_VERSION
diff --git a/README.md b/README.md
index f606ce46..d4d9bc17 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ together with snapshots.
 
 ## Usage
 
-`sharedb-mongo` wraps native [mongodb](https://github.com/mongodb/node-mongodb-native), and it supports the same configuration options.
+`sharedb-mongo` uses the [MongoDB NodeJS Driver](https://github.com/mongodb/node-mongodb-native), and it supports the same configuration options.
 
 There are two ways to instantiate a sharedb-mongo wrapper:
 
@@ -33,7 +33,7 @@ There are two ways to instantiate a sharedb-mongo wrapper:
 arguments as arguments to the module function. For example:
 
     ```javascript
-    const db = require('sharedb-mongo')('mongodb://localhost:27017/test');
+    const db = require('sharedb-mongo')('mongodb://localhost:27017/test', {mongoOptions: {...}});
     const backend = new ShareDB({db});
     ```
 
diff --git a/index.js b/index.js
index db1e4b2a..ad9e8dfa 100644
--- a/index.js
+++ b/index.js
@@ -58,7 +58,9 @@ function ShareDbMongo(mongo, options) {
     // We can only get the mongodb client instance in a callback, so
     // buffer up any requests received in the meantime
     this.mongo = null;
+    this._mongoClient = null;
     this.mongoPoll = null;
+    this._mongoPollClient = null;
     this.pendingConnect = [];
     this._connect(mongo, options);
   } else {
@@ -125,6 +127,16 @@ ShareDbMongo.prototype._flushPendingConnect = function() {
   }
 };
 
+function isLegacyMongoClient(client) {
+  // mongodb 2.0 connect returns a DB object that also implements the
+  // functionality of client, such as `close()`. mongodb 3.0 connect returns a
+  // Client without the `collection()` method
+  return (
+    typeof client.collection === 'function' &&
+    typeof client.close === 'function'
+  );
+}
+
 ShareDbMongo.prototype._connect = function(mongo, options) {
   // Create the mongo connection client connections if needed
   //
@@ -137,32 +149,49 @@ ShareDbMongo.prototype._connect = function(mongo, options) {
       tasks = {mongo: mongo, mongoPoll: options.mongoPoll};
     } else {
       tasks = {
-        mongo: function(parallelCb) {
+        mongoClient: function(parallelCb) {
           mongodb.connect(mongo, options.mongoOptions, parallelCb);
         },
-        mongoPoll: function(parallelCb) {
+        mongoPollClient: function(parallelCb) {
           mongodb.connect(options.mongoPoll, options.mongoPollOptions, parallelCb);
         }
       };
     }
     async.parallel(tasks, function(err, results) {
       if (err) throw err;
-      self.mongo = results.mongo;
-      self.mongoPoll = results.mongoPoll;
+      var mongoClient = results.mongoClient;
+      var mongoPollClient = results.mongoPollClient;
+      if (isLegacyMongoClient(mongoClient)) {
+        self.mongo = self._mongoClient = mongoClient;
+        self.mongoPoll = self._mongoPollClient = mongoPollClient;
+      } else {
+        self.mongo = mongoClient.db();
+        self._mongoClient = mongoClient;
+        self.mongoPoll = mongoPollClient.db();
+        self._mongoPollClient = mongoPollClient;
+      }
       self._flushPendingConnect();
     });
     return;
   }
-  var finish = function(err, db) {
+  var finish = function(err, client) {
     if (err) throw err;
-    self.mongo = db;
+    if (isLegacyMongoClient(client)) {
+      self.mongo = self._mongoClient = client;
+    } else {
+      self.mongo = client.db();
+      self._mongoClient = client;
+    }
     self._flushPendingConnect();
   };
   if (typeof mongo === 'function') {
     mongo(finish);
     return;
   }
-  mongodb.connect(mongo, options, finish);
+  // TODO: Don't pass options directly to mongodb.connect();
+  // only pass options.mongoOptions
+  var mongoOptions = options.mongoOptions || options;
+  mongodb.connect(mongo, mongoOptions, finish);
 };
 
 ShareDbMongo.prototype.close = function(callback) {
@@ -172,13 +201,13 @@ ShareDbMongo.prototype.close = function(callback) {
     };
   }
   var self = this;
-  this.getDbs(function(err, mongo, mongoPoll) {
+  this.getDbs(function(err) {
     if (err) return callback(err);
     self.closed = true;
-    mongo.close(function(err) {
+    self._mongoClient.close(function(err) {
       if (err) return callback(err);
-      if (!mongoPoll) return callback();
-      mongoPoll.close(callback);
+      if (!self._mongoPollClient) return callback();
+      self._mongoPollClient.close(callback);
     });
   });
 };
diff --git a/package.json b/package.json
index 49e729df..580ae575 100644
--- a/package.json
+++ b/package.json
@@ -4,25 +4,25 @@
   "description": "MongoDB database adapter for ShareDB",
   "main": "index.js",
   "dependencies": {
-    "async": "^1.4.2",
-    "mongodb": "^2.1.2",
+    "async": "^1.5.2",
+    "mongodb": "^2.1.2 || ^3.0.0",
     "sharedb": "^1.0.0-beta"
   },
   "devDependencies": {
-    "coveralls": "^2.11.8",
+    "chai": "^4.2.0",
+    "coveralls": "^3.0.7",
     "eslint": "^5.16.0",
     "eslint-config-google": "^0.13.0",
-    "expect.js": "^0.3.1",
-    "istanbul": "^0.4.2",
-    "mocha": "^2.3.3",
-    "sharedb-mingo-memory": "^1.0.2",
+    "mocha": "^6.2.2",
+    "nyc": "^14.1.1",
+    "sharedb-mingo-memory": "^1.1.1",
     "sinon": "^6.1.5"
   },
   "scripts": {
     "lint": "./node_modules/.bin/eslint --ignore-path .gitignore '**/*.js'",
     "lint:fix": "npm run lint -- --fix",
     "test": "npm run lint && node_modules/.bin/mocha",
-    "test-cover": "npm run lint && node_modules/istanbul/lib/cli.js cover node_modules/mocha/bin/_mocha"
+    "test-cover": "npm run lint && node_modules/nyc/bin/nyc.js --temp-dir=coverage -r text -r lcov node_modules/mocha/bin/_mocha"
   },
   "repository": "git://github.com/share/sharedb-mongo.git",
   "author": "Nate Smith and Joseph Gentle",
diff --git a/test/test_get_ops_without_strict_linking.js b/test/test_get_ops_without_strict_linking.js
index c789946c..9616b9c0 100644
--- a/test/test_get_ops_without_strict_linking.js
+++ b/test/test_get_ops_without_strict_linking.js
@@ -1,5 +1,4 @@
-var expect = require('expect.js');
-var mongodb = require('mongodb');
+var expect = require('chai').expect;
 var ShareDbMongo = require('..');
 var getQuery = require('sharedb-mingo-memory/get-query');
 var sinon = require('sinon');
@@ -7,19 +6,17 @@ var sinon = require('sinon');
 var mongoUrl = process.env.TEST_MONGO_URL || 'mongodb://localhost:27017/test';
 
 function create(callback) {
-  var db = new ShareDbMongo({
-    mongo: function(shareDbCallback) {
-      mongodb.connect(mongoUrl, function(err, mongo) {
-        if (err) return callback(err);
-        mongo.dropDatabase(function(err) {
-          if (err) return callback(err);
-          shareDbCallback(null, mongo);
-          callback(null, db, mongo);
-        });
-      });
-    },
+  var db = new ShareDbMongo(mongoUrl, {
+    mongoOptions: {},
     getOpsWithoutStrictLinking: true
   });
+  db.getDbs(function(err, mongo) {
+    if (err) return callback(err);
+    mongo.dropDatabase(function(err) {
+      if (err) return callback(err);
+      callback(null, db, mongo);
+    });
+  });
 };
 
 require('sharedb/test/db')({create: create, getQuery: getQuery});
@@ -67,11 +64,11 @@ describe('getOpsWithoutStrictLinking: true', function() {
     it('fetches ops 0-1 without fetching all ops', function(done) {
       db.getOps(collection, id, 0, 2, null, function(error, ops) {
         if (error) return done(error);
-        expect(ops.length).to.be(2);
-        expect(ops[0].v).to.be(0);
-        expect(ops[1].v).to.be(1);
-        expect(db._getSnapshotOpLink.notCalled).to.be(true);
-        expect(db._getOps.calledOnceWith(collection, id, 0, 2)).to.be(true);
+        expect(ops.length).to.equal(2);
+        expect(ops[0].v).to.equal(0);
+        expect(ops[1].v).to.equal(1);
+        expect(db._getSnapshotOpLink.notCalled).to.equal(true);
+        expect(db._getOps.calledOnceWith(collection, id, 0, 2)).to.equal(true);
         done();
       });
     });
@@ -81,16 +78,16 @@ describe('getOpsWithoutStrictLinking: true', function() {
 
       callInSeries([
         function(next) {
-          mongo.collection('o_' + collection).insert(spuriousOp, next);
+          mongo.collection('o_' + collection).insertOne(spuriousOp, next);
         },
         function(result, next) {
           db.getOps(collection, id, 0, 2, null, next);
         },
         function(ops, next) {
-          expect(ops.length).to.be(2);
-          expect(ops[1].oi).to.be('bar');
-          expect(db._getSnapshotOpLink.notCalled).to.be(true);
-          expect(db._getOps.calledOnceWith(collection, id, 0, 2)).to.be(true);
+          expect(ops.length).to.equal(2);
+          expect(ops[1].oi).to.equal('bar');
+          expect(db._getSnapshotOpLink.notCalled).to.equal(true);
+          expect(db._getOps.calledOnceWith(collection, id, 0, 2)).to.equal(true);
           next();
         },
         done
@@ -102,16 +99,16 @@ describe('getOpsWithoutStrictLinking: true', function() {
 
       callInSeries([
         function(next) {
-          mongo.collection('o_' + collection).insert(spuriousOp, next);
+          mongo.collection('o_' + collection).insertOne(spuriousOp, next);
         },
         function(result, next) {
           db.getOps(collection, id, 0, 2, null, next);
         },
         function(ops, next) {
-          expect(ops.length).to.be(2);
-          expect(ops[1].oi).to.be('bar');
-          expect(db._getSnapshotOpLink.notCalled).to.be(true);
-          expect(db._getOps.calledOnceWith(collection, id, 0, 3)).to.be(true);
+          expect(ops.length).to.equal(2);
+          expect(ops[1].oi).to.equal('bar');
+          expect(db._getSnapshotOpLink.notCalled).to.equal(true);
+          expect(db._getOps.calledOnceWith(collection, id, 0, 3)).to.equal(true);
           next();
         },
         done
@@ -134,10 +131,10 @@ describe('getOpsWithoutStrictLinking: true', function() {
           db.getOps(collection, id, 0, 2, null, next);
         },
         function(ops, next) {
-          expect(ops.length).to.be(2);
+          expect(ops.length).to.equal(2);
           expect(ops[0].create).to.eql({});
-          expect(ops[1].oi).to.be('bar');
-          expect(db._getSnapshotOpLink.calledOnce).to.be(true);
+          expect(ops[1].oi).to.equal('bar');
+          expect(db._getSnapshotOpLink.calledOnce).to.equal(true);
           next();
         },
         done
diff --git a/test/test_mongo.js b/test/test_mongo.js
index 7a75c955..4b8ba60e 100644
--- a/test/test_mongo.js
+++ b/test/test_mongo.js
@@ -1,21 +1,18 @@
-var expect = require('expect.js');
-var mongodb = require('mongodb');
-var ShareDbMongo = require('../index');
+var expect = require('chai').expect;
+var ShareDbMongo = require('..');
 var getQuery = require('sharedb-mingo-memory/get-query');
 
 var mongoUrl = process.env.TEST_MONGO_URL || 'mongodb://localhost:27017/test';
 
 function create(callback) {
-  var db = new ShareDbMongo({mongo: function(shareDbCallback) {
-    mongodb.connect(mongoUrl, function(err, mongo) {
+  var db = new ShareDbMongo(mongoUrl);
+  db.getDbs(function(err, mongo) {
+    if (err) return callback(err);
+    mongo.dropDatabase(function(err) {
       if (err) return callback(err);
-      mongo.dropDatabase(function(err) {
-        if (err) return callback(err);
-        shareDbCallback(null, mongo);
-        callback(null, db, mongo);
-      });
+      callback(null, db, mongo);
     });
-  }});
+  });
 };
 
 require('sharedb/test/db')({create: create, getQuery: getQuery});
@@ -43,9 +40,9 @@ describe('mongo db', function() {
         mongo.collection('o_testcollection').indexInformation(function(err, indexes) {
           if (err) return done(err);
           // Index for getting document(s) ops
-          expect(indexes['d_1_v_1']).ok();
+          expect(indexes['d_1_v_1']).ok;
           // Index for checking committed op(s) by src and seq
-          expect(indexes['src_1_seq_1_v_1']).ok();
+          expect(indexes['src_1_seq_1_v_1']).ok;
           done();
         });
       });
@@ -70,9 +67,9 @@ describe('mongo db', function() {
     it('does not allow editing the system collection', function(done) {
       var db = this.db;
       db.commit('system', 'test', {v: 0, create: {}}, {}, null, function(err) {
-        expect(err).ok();
+        expect(err).ok;
         db.getSnapshot('system', 'test', null, null, function(err) {
-          expect(err).ok();
+          expect(err).ok;
           done();
         });
       });
@@ -85,21 +82,21 @@ describe('mongo db', function() {
 
     it('does not allow $where queries', function(done) {
       this.db.query('testcollection', {$where: 'true'}, null, null, function(err) {
-        expect(err).ok();
+        expect(err).ok;
         done();
       });
     });
 
     it('queryPollDoc does not allow $where queries', function(done) {
       this.db.queryPollDoc('testcollection', 'somedoc', {$where: 'true'}, null, function(err) {
-        expect(err).ok();
+        expect(err).ok;
         done();
       });
     });
 
     it('$query is deprecated', function(done) {
       this.db.query('testcollection', {$query: {}}, null, null, function(err) {
-        expect(err).ok();
+        expect(err).ok;
         expect(err.code).eql(4106);
         done();
       });
@@ -107,7 +104,7 @@ describe('mongo db', function() {
 
     it('only one collection operation allowed', function(done) {
       this.db.query('testcollection', {$distinct: {y: 1}, $aggregate: {}}, null, null, function(err) {
-        expect(err).ok();
+        expect(err).ok;
         expect(err.code).eql(4108);
         done();
       });
@@ -115,7 +112,7 @@ describe('mongo db', function() {
 
     it('only one cursor operation allowed', function(done) {
       this.db.query('testcollection', {$count: true, $explain: true}, null, null, function(err) {
-        expect(err).ok();
+        expect(err).ok;
         expect(err.code).eql(4109);
         done();
       });
@@ -123,7 +120,7 @@ describe('mongo db', function() {
 
     it('cursor transform can\'t run after collection operation', function(done) {
       this.db.query('testcollection', {$distinct: {y: 1}, $sort: {y: 1}}, null, null, function(err) {
-        expect(err).ok();
+        expect(err).ok;
         expect(err.code).eql(4110);
         done();
       });
@@ -131,7 +128,7 @@ describe('mongo db', function() {
 
     it('cursor operation can\'t run after collection operation', function(done) {
       this.db.query('testcollection', {$distinct: {y: 1}, $count: true}, null, null, function(err) {
-        expect(err).ok();
+        expect(err).ok;
         expect(err.code).eql(4110);
         done();
       });
@@ -139,7 +136,7 @@ describe('mongo db', function() {
 
     it('non-object $readPref should return error', function(done) {
       this.db.query('testcollection', {$readPref: true}, null, null, function(err) {
-        expect(err).ok();
+        expect(err).ok;
         expect(err.code).eql(4107);
         done();
       });
@@ -148,7 +145,7 @@ describe('mongo db', function() {
     it('malformed $mapReduce should return error', function(done) {
       this.db.allowJSQueries = true; // required for $mapReduce
       this.db.query('testcollection', {$mapReduce: true}, null, null, function(err) {
-        expect(err).ok();
+        expect(err).ok;
         expect(err.code).eql(4107);
         done();
       });
@@ -255,7 +252,7 @@ describe('mongo db', function() {
         {$sort: {count: 1}}
       ]};
       this.db.query('testcollection', query, null, null, function(err) {
-        expect(err).ok();
+        expect(err).ok;
         done();
       });
     });
@@ -286,7 +283,7 @@ describe('mongo db', function() {
               }
             };
             db.query('testcollection', query, null, null, function(err) {
-              expect(err).ok();
+              expect(err).ok;
               done();
             });
           });
diff --git a/test/test_op_link_validator.js b/test/test_op_link_validator.js
index c2366aed..70aa8324 100644
--- a/test/test_op_link_validator.js
+++ b/test/test_op_link_validator.js
@@ -1,16 +1,16 @@
 var OpLinkValidator = require('../op-link-validator');
-var expect = require('expect.js');
+var expect = require('chai').expect;
 
 describe('OpLinkValidator', function() {
   it('starts with no unique op', function() {
     var validator = new OpLinkValidator();
     var opWithUniqueVersion = validator.opWithUniqueVersion();
-    expect(opWithUniqueVersion).to.be(null);
+    expect(opWithUniqueVersion).to.equal(null);
   });
 
   it('starts not at the end of the list', function() {
     var validator = new OpLinkValidator();
-    expect(validator.isAtEndOfList()).to.be(false);
+    expect(validator.isAtEndOfList()).to.equal(false);
   });
 
   it('has no unique op with just one op', function() {
@@ -20,7 +20,7 @@ describe('OpLinkValidator', function() {
     validator.push(op);
     var opWithUniqueVersion = validator.opWithUniqueVersion();
 
-    expect(opWithUniqueVersion).to.be(null);
+    expect(opWithUniqueVersion).to.equal(null);
   });
 
   it('has a unique op with just two different ops', function() {
@@ -32,7 +32,7 @@ describe('OpLinkValidator', function() {
     validator.push(op2);
     var opWithUniqueVersion = validator.opWithUniqueVersion();
 
-    expect(opWithUniqueVersion).to.be(op1);
+    expect(opWithUniqueVersion).to.equal(op1);
   });
 
   it('does not have a uniquye op with just two identical ops', function() {
@@ -44,7 +44,7 @@ describe('OpLinkValidator', function() {
     validator.push(op2);
     var opWithUniqueVersion = validator.opWithUniqueVersion();
 
-    expect(opWithUniqueVersion).to.be(null);
+    expect(opWithUniqueVersion).to.equal(null);
   });
 
   it('has a unique op with three ops with different versions', function() {
@@ -58,7 +58,7 @@ describe('OpLinkValidator', function() {
     validator.push(op3);
     var opWithUniqueVersion = validator.opWithUniqueVersion();
 
-    expect(opWithUniqueVersion).to.be(op2);
+    expect(opWithUniqueVersion).to.equal(op2);
   });
 
   it('is not at the end of the list with three ops', function() {
@@ -71,7 +71,7 @@ describe('OpLinkValidator', function() {
     validator.push(op2);
     validator.push(op3);
 
-    expect(validator.isAtEndOfList()).to.be(false);
+    expect(validator.isAtEndOfList()).to.equal(false);
   });
 
   it('does not have a unique op with three ops with the same version', function() {
@@ -83,7 +83,7 @@ describe('OpLinkValidator', function() {
     validator.push(op);
     var opWithUniqueVersion = validator.opWithUniqueVersion();
 
-    expect(opWithUniqueVersion).to.be(null);
+    expect(opWithUniqueVersion).to.equal(null);
   });
 
   it('does not have a unique op if the first two ops are the same', function() {
@@ -97,7 +97,7 @@ describe('OpLinkValidator', function() {
     validator.push(op3);
     var opWithUniqueVersion = validator.opWithUniqueVersion();
 
-    expect(opWithUniqueVersion).to.be(null);
+    expect(opWithUniqueVersion).to.equal(null);
   });
 
   it('does not have a unique op if the last two ops are the same', function() {
@@ -111,7 +111,7 @@ describe('OpLinkValidator', function() {
     validator.push(op3);
     var opWithUniqueVersion = validator.opWithUniqueVersion();
 
-    expect(opWithUniqueVersion).to.be(null);
+    expect(opWithUniqueVersion).to.equal(null);
   });
 
   it('has a unique op in a long chain', function() {
@@ -133,7 +133,7 @@ describe('OpLinkValidator', function() {
     validator.push(op7);
     var opWithUniqueVersion = validator.opWithUniqueVersion();
 
-    expect(opWithUniqueVersion).to.be(op6);
+    expect(opWithUniqueVersion).to.equal(op6);
   });
 
   it('has a unique op with two ops and a current op of null', function() {
@@ -147,13 +147,13 @@ describe('OpLinkValidator', function() {
     validator.push(op3);
     var opWithUniqueVersion = validator.opWithUniqueVersion();
 
-    expect(opWithUniqueVersion).to.be(op2);
+    expect(opWithUniqueVersion).to.equal(op2);
   });
 
   it('is at the end of the list with a current op of null', function() {
     var op = null;
     var validator = new OpLinkValidator();
     validator.push(op);
-    expect(validator.isAtEndOfList()).to.be(true);
+    expect(validator.isAtEndOfList()).to.equal(true);
   });
 });
diff --git a/test/test_skip_poll.js b/test/test_skip_poll.js
index fae3a679..587f4058 100644
--- a/test/test_skip_poll.js
+++ b/test/test_skip_poll.js
@@ -1,4 +1,4 @@
-var expect = require('expect.js');
+var expect = require('chai').expect;
 var ShareDbMongo = require('../index');
 
 describe('skipPoll', function() {
