I'm having difficulty testing a NestJS service which utilises a simple entity that only has one relationship. The related entity, however, is related to many other entities which in turn are also related to other entities. I don't want to import all of the entities and pass to TypeOrmModule.forRoot, and if I don't I get an error like the following:
Entity metadata for Wallet#customer was not found.
For every entity that has not been imported. Is it possible to "mock" the relationship in some way?
Test:
const module: TestingModule = await Test.createTestingModule({
imports: [
TypeOrmModule.forRoot(CONNECTION_OPTIONS),
TypeOrmModule.forFeature([WalletEntity, WalletTransactionEntity]),
],
providers: [WalletService],
}).compile();
Entity
#Entity('wallet')
export class WalletEntity extends BaseEntity {
#Column()
customerId: string;
#OneToOne(() => CustomerEntity, (customer) => customer.wallet)
customer: CustomerEntity;
#OneToMany(() => WalletTransactionEntity, (transaction) => transaction.wallet)
transactions: WalletTransactionEntity[];
}
For unit tests, I would suggest not connecting to a live database or using any imports, but instead just using custom providers to mock the dependencies of the service you're testing. This repository has a bunch of examples including some for TypeORM. A very basic setup, based on the imports you have, might be something like
beforeEach(async () => {
const modRef = await Test.createTestingModule({
providers: [
WalletService,
{
provide: getRepositoryToken(WalletEntity),
useValue: walletRepoMock,
},
{
provide: getRepositoryToken(WalletTransactionEntity),
useValue: walletTransactionRepoMock,
}
]
}).compile();
});
Now you can replace walletRepoMock and walletTransactionRepoMock with mock implementation of the methods you use for Repository and you'll be good to go
Types are added to Mongoose 5.10.5 and it seems they are different from #types/mongoose. Then my code is not running anymore:
import MongooseDeletePlugin from 'mongoose-delete';
...
#Module({
imports: [
MongooseModule.forRoot(MONGO_URI, {
connectionFactory: (connection: Connection) => {
connection.plugin(MongooseDeletePlugin, {
// ^--- This generates error: "Property 'plugin' does not exist on type 'Connection'."
overrideMethods: 'all',
deletedAt: true,
indexFields: ['deleted'],
});
return connection;
},
}),
...
In Mongoose documentations, this is the correct way to add a global plugin. But How can I add it to the NestJS?
const mongoose = require('mongoose');
mongoose.plugin(require('./loadedAt'));
Below is my PurchaseOrder model defined in sequelize. I want to update the Supplier Model whenever there is an update to the PurchaseOrder. I thought of using the hooks to achieve this. But I couldn't able to access another model inside this model. I tried importing and all stuff, but no luck. Is this the right way to use the hooks or what should I use to achieve the same? Any help or direction is much appreciated!
module.exports = (sequelize, Sequelize) => {
const PurchaseOrder = sequelize.define("purchaseOrder", {
totalAmount: {
type: Sequelize.INTEGER
},
paid: {
type: Sequelize.BOOLEAN
},
paymentMode: {
type: Sequelize.ENUM('CASH', 'CHEQUE', 'BANK', 'CARD', 'NA')
}
}, {
freezeTableName: true,
hooks: {
beforeUpdate: (order, options) => {
// here I want to update the another model(Supplier).
// But I couldn't able to access another model inside the hook
Supplier.increment('balance'{
where: { id: order.supplierId }
});
}
}
});
return PurchaseOrder;
};
In my code I have a couple hooks that update other models (audit logging of changes for example). You need to make sure to pass along the options.transaction so that any changes are rolled back if there is an error later in the chain.
This example accesses another table keyed by other_model. When the hooks run the models should all already be registered with Sequelize.
module.exports = function Order(sequelize, DataTypes) {
const Order = sequelize.define(
'order',
{ /* columns */ },
{
hooks: {
beforeUpdate: async function(order, options) {
// get the transaction, if it is set
const { transaction } = options;
// use sequelize.models and make sure to pass the
// transaction so it is rolled back if there is an error
await sequelize.models.supplier.increment(balance, {
where: { id: order.supplierId },
transaction,
});
},
},
},
});
return Order;
}
You can try sequelize['Supplier'] because all models should be already registered in an Sequelize instance.
Nevertheless I suppose it's not a good idea to make modifications in a DB via other models in such hooks because in such cases you should take into account that all operations should be done in the same transaction i.e. should be executed as an atomic operation to avoid inconsistent state of data in a DB if some modifications fail.
Not a relatable answer, but if anyone wants to try querying a model to another model using validate custom functions. You can define your model like sequelize.models.ModelName sequelize shouldn't be imported like require('sequelize') but it should use the sequelize parameter defined in module.exports of your current model.
await sequelize.models.ModelName.findAll()
I need to create a mongoose connection to get db.stats(). I follow the nestjs tutorial to get data from database with mongoose and providers method. But I don´t get stats because this method create a relationship between mongoose and models.
I write this lines in my method, but the code is very acoplated:
async getStatsFromDatabase(): Promise<IDatabaseStats> {
await mongoose.connect(env.base.mongodb.uri);
const stats = await mongoose.connection.db.stats();
await mongoose.disconnect();
return stats;
}
how can get a connection with a provider without a mongoose schema?
Thank you!
I write the answer in case someone is interested:
Provider:
`const provider = {
provide: config.health.providers.mongodb,
useFactory: (connection: Connection): Connection => connection['connection'],
inject: [env.providers.database.mongodb],
},`
We should return connection.
Given a simple Mongoose model:
import mongoose, { Schema } from 'mongoose';
const PostSchema = Schema({
title: { type: String },
postDate: { type: Date, default: Date.now }
}, { timestamps: true });
const Post = mongoose.model('Post', PostSchema);
export default Post;
I wish to test this model, but I'm hitting a few roadblocks.
My current spec looks something like this (some stuff omitted for brevity):
import mongoose from 'mongoose';
import { expect } from 'chai';
import { Post } from '../../app/models';
describe('Post', () => {
beforeEach((done) => {
mongoose.connect('mongodb://localhost/node-test');
done();
});
describe('Given a valid post', () => {
it('should create the post', (done) => {
const post = new Post({
title: 'My test post',
postDate: Date.now()
});
post.save((err, doc) => {
expect(doc.title).to.equal(post.title)
expect(doc.postDate).to.equal(post.postDate);
done();
});
});
});
});
However, with this I'm hitting my database every time I run the test, which I would prefer to avoid.
I've tried using Mockgoose, but then my test won't run.
import mockgoose from 'mockgoose';
// in before or beforeEach
mockgoose(mongoose);
The test gets stuck and throws an error saying: Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test. I've tried increasing the timeout to 20 seconds but that didn't solve anything.
Next, I threw away Mockgoose and tried using Sinon to stub the save call.
describe('Given a valid post', () => {
it('should create the post', (done) => {
const post = new Post({
title: 'My test post',
postDate: Date.now()
});
const stub = sinon.stub(post, 'save', function(cb) { cb(null) })
post.save((err, post) => {
expect(stub).to.have.been.called;
done();
});
});
});
This test passes, but it somehow doesn't make much sense to me. I'm quite new to stubbing, mocking, what have you, ... and I'm not sure if this is the right way to go. I'm stubbing the save method on post, and then I'm asserting it to have been called, but I'm obviously calling it... Also, I can't seem to get to the arguments the non-stubbed Mongoose method would return. I would like to compare the post variable to something the save method returns, like in the very first test where I hit the database. I've tried a couple of methods but they all feel quite hackish. There must be a clean way, no?
Couple of questions:
Should I indeed avoid hitting the database like I've always read everywhere? My first example works fine and I could clear the database after each run. However, it doesn't really feel right to me.
How would I stub the save method from the Mongoose model and make sure it actually tests what I want to test: saving a new object to the db.
The basics
In unit testing one should not hit the DB. I could think of one exception: hitting an in-memory DB, but even that lies already in the area of integration testing as you would only need the state saved in memory for complex processes (and thus not really units of functionality). So, yes no actual DB.
What you want to test in unit tests is that your business logic results in correct API calls at the interface between your application and the DB. You can and probably should assume that the DB API/driver developers have done a good job testing that everything below the API behaves as expected. However, you also want to cover in your tests how your business logic reacts to different valid API results such as successful saves, failures due to data consistency, failures due to connection issues etc.
This means that what you need and want to mock is everything that is below the DB driver interface. You would, however, need to model that behaviour so that your business logic can be tested for all outcomes of the DB calls.
Easier said than done because this means you need to have access to the API via the technology you use and you need to know the API.
The reality of mongoose
Sticking to the basics we want to mock the calls performed by the underlying 'driver' that mongoose uses. Assuming it is node-mongodb-native we need to mock out those calls. Understanding the full interplay between mongoose and the native driver is not easy, but it generally comes down to the methods in mongoose.Collection because the latter extends mongoldb.Collection and does not reimplement methods like insert. If we are able to control the behaviour of insert in this particular case, then we know we mocked out the DB access at the API level. You can trace it in the source of both projects, that Collection.insert is really the native driver method.
For your particular example I created a public Git repository with a complete package, but I will post all of the elements here in the answer.
The solution
Personally I find the "recommended" way of working with mongoose quite unusable: models are usually created in the modules where the corresponding schemas are defined, yet they already need a connection. For purposes of having multiple connections to talk to completely different mongodb databases in the same project and for testing purposes this makes life really hard. In fact, as soon as concerns are fully separated mongoose, at least to me, becomes nearly unusable.
So the first thing I create is the package description file, a module with a schema and a generic "model generator":
package.json
{
"name": "xxx",
"version": "0.1.0",
"private": true,
"main": "./src",
"scripts": {
"test" : "mocha --recursive"
},
"dependencies": {
"mongoose": "*"
},
"devDependencies": {
"mocha": "*",
"chai": "*"
}
}
src/post.js
var mongoose = require("mongoose");
var PostSchema = new mongoose.Schema({
title: { type: String },
postDate: { type: Date, default: Date.now }
}, {
timestamps: true
});
module.exports = PostSchema;
src/index.js
var model = function(conn, schema, name) {
var res = conn.models[name];
return res || conn.model.bind(conn)(name, schema);
};
module.exports = {
PostSchema: require("./post"),
model: model
};
Such a model generator has its drawbacks: there are elements that may need to be attached to the model and it would make sense to place them in the same module where the schema is created. So finding a generic way to add those is a bit tricky. For example, a module could export post-actions to be automatically run when a model is generated for a given connection etc. (hacking).
Now let's mock the API. I'll keep it simple and will only mock what I need for the tests in question. It is essential that I would like to mock out the API in general, not individual methods of individual instances. The latter might be useful in some cases, or when nothing else helps, but I would need to have access to objects created inside of my business logic (unless injected or provided via some factory pattern), and this would mean modifying the main source. At the same time, mocking the API in one place has a drawback: it is a generic solution, which would probably implement successful execution. For testing error cases, mocking in instances in the tests themselves could be required, but then within your business logic you might not have direct access to the instance of e.g. post created deep inside.
So, let's have a look at the general case of mocking successful API call:
test/mock.js
var mongoose = require("mongoose");
// this method is propagated from node-mongodb-native
mongoose.Collection.prototype.insert = function(docs, options, callback) {
// this is what the API would do if the save succeeds!
callback(null, docs);
};
module.exports = mongoose;
Generally, as long as models are created after modifying mongoose, it is thinkable that the above mocks are done on per test basis to simulate any behaviour. Make sure to revert to the original behaviour, however, before every test!
Finally this is how our tests for all possible data saving operations could look like. Pay attention, these are not specific to our Post model and could be done for all other models with exactly the same mock in place.
test/test_model.js
// now we have mongoose with the mocked API
// but it is essential that our models are created AFTER
// the API was mocked, not in the main source!
var mongoose = require("./mock"),
assert = require("assert");
var underTest = require("../src");
describe("Post", function() {
var Post;
beforeEach(function(done) {
var conn = mongoose.createConnection();
Post = underTest.model(conn, underTest.PostSchema, "Post");
done();
});
it("given valid data post.save returns saved document", function(done) {
var post = new Post({
title: 'My test post',
postDate: Date.now()
});
post.save(function(err, doc) {
assert.deepEqual(doc, post);
done(err);
});
});
it("given valid data Post.create returns saved documents", function(done) {
var post = new Post({
title: 'My test post',
postDate: 876543
});
var posts = [ post ];
Post.create(posts, function(err, docs) {
try {
assert.equal(1, docs.length);
var doc = docs[0];
assert.equal(post.title, doc.title);
assert.equal(post.date, doc.date);
assert.ok(doc._id);
assert.ok(doc.createdAt);
assert.ok(doc.updatedAt);
} catch (ex) {
err = ex;
}
done(err);
});
});
it("Post.create filters out invalid data", function(done) {
var post = new Post({
foo: 'Some foo string',
postDate: 876543
});
var posts = [ post ];
Post.create(posts, function(err, docs) {
try {
assert.equal(1, docs.length);
var doc = docs[0];
assert.equal(undefined, doc.title);
assert.equal(undefined, doc.foo);
assert.equal(post.date, doc.date);
assert.ok(doc._id);
assert.ok(doc.createdAt);
assert.ok(doc.updatedAt);
} catch (ex) {
err = ex;
}
done(err);
});
});
});
It is essential to note that we are still testing the very low level functionality, but we can use this same approach for testing any business logic that uses Post.create or post.save internally.
The very final bit, let's run the tests:
~/source/web/xxx $ npm test
> xxx#0.1.0 test /Users/osklyar/source/web/xxx
> mocha --recursive
Post
✓ given valid data post.save returns saved document
✓ given valid data Post.create returns saved documents
✓ Post.create filters out invalid data
3 passing (52ms)
I must say, this is no fun to do it that way. But this way it is really pure unit-testing of the business logic without any in-memory or real DBs and fairly generic.
If what you want is test static's and method's of certain Mongoose model, I would recommend you to use sinon and sinon-mongoose. (I guess it's compatible with chai)
This way, you won't need to connect to Mongo DB.
Following your example, suppose you have a static method findLast
//If you are using callbacks
PostSchema.static('findLast', function (n, callback) {
this.find().limit(n).sort('-postDate').exec(callback);
});
//If you are using Promises
PostSchema.static('findLast', function (n) {
this.find().limit(n).sort('-postDate').exec();
});
Then, to test this method
var Post = mongoose.model('Post');
// If you are using callbacks, use yields so your callback will be called
sinon.mock(Post)
.expects('find')
.chain('limit').withArgs(10)
.chain('sort').withArgs('-postDate')
.chain('exec')
.yields(null, 'SUCCESS!');
Post.findLast(10, function (err, res) {
assert(res, 'SUCCESS!');
});
// If you are using Promises, use 'resolves' (using sinon-as-promised npm)
sinon.mock(Post)
.expects('find')
.chain('limit').withArgs(10)
.chain('sort').withArgs('-postDate')
.chain('exec')
.resolves('SUCCESS!');
Post.findLast(10).then(function (res) {
assert(res, 'SUCCESS!');
});
You can find working (and simple) examples on the sinon-mongoose repo.