Fullstacking: Connecting NodeJS ↔ MongoDB
(Source/Credits: https://dev.to/heymarkkop/fullstacking-connecting-nodejs-mongodb-32l4)
With React Native, NodeJS + KoaJS and MongoDB set up, we can start connecting them with each other....
With React Native, NodeJS + KoaJS and MongoDB set up, we can start connecting them with each other.
KoaJS ↔ MongoDB
As I want to learn and practice Test-Driven Development, we'll be creating tests first and it seems that Mocha is recommended over Jest to test Mongoose (which we'll be using later). We'll also be using supertest for integration testing.
yarn add mocha --dev
yarn add supertest --dev
Create a test file like this:
```javascript // server.test.js const request = require('supertest'); const app = require('./koa');
describe('Server', () => { it('is running', done => { request(app) .get('/') .expect(200, done); }); }); ```
and change package.json
as following:
javascript
// package.json
...
"scripts": {
...
"test": "mocha server.test.js --watch",
...
},
...
I've used mocha './server/*.test.js' --recursive --watch
instead so it runs all test files inside server folder. We'll probably change this later.
Run yarn test
and find that TypeError: app.address is not a function
because app doesn't exist yet thus it's time to write the actual code
```javascript // server.js const Koa = require('koa');
const app = new Koa();
app.use(async ctx => { ctx.body = "Hello World, I'm Koa"; });
module.exports = app.listen(3000, () =>
console.log('Running on http://localhost:3000/'),
);
``
Don't forget to
module.exportsit.
Now our first test is passing, but it can throw
Uncaught Error: listen EADDRINUSE: address already in use :::3000when trying to run the test again or **--watch**ing it.
We have to [close](https://github.com/visionmedia/supertest/issues/437) the server after each test, so add this inside
describe()` block
javascript
// server.test.js
...
afterEach(() => {
app.close();
});
...
With Koa working and being tested, we can now try to read some information from our MongoDB instance.
Let's try to find our stampler by adding this test
javascript
// server.test.js
...
it('finds our stampler', done => {
request(app)
.get('/')
.expect(/Stampler/)
.expect(200, done);
});
It'll return Error: expected body 'Hello World, I\'m Koa' to match /stampler/
because ctx.body
is a plain text, not the data in our database.
To access it, we'll use Mongoose
yarn add mongoose
or npm install mongoose
Create a Product.js
to define a new schema
```javascript
// Product.js
var mongoose = require('mongoose');
const ProductSchema = mongoose.Schema({ title: String, });
module.exports = mongoose.model('Product', ProductSchema); ```
And use it as it follows ```javascript // server.js const Koa = require('koa'); const mongoose = require('mongoose'); const Product = require('./Product');
const app = new Koa();
mongoose.connect('mongodb://127.0.0.1:27017/test', {useNewUrlParser: true});
app.use(async ctx => { //ctx.body = "Hello World, I'm Koa"; ctx.body = await Product.find({}); });
module.exports = app.listen(3000, () => console.log('Running on http://localhost:3000/'), );
```
Note that your MongoDB should be running or you'll get
MongoNetworkError: failed to connect to server [127.0.0.1:27017] on first connect [MongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017]
You can check it by running mongo
in the terminal and
sudo services mongodb start
to start it.
It should work at first, however you might notice an error if --watching the test suite:
OverwriteModelError: Cannot overwrite 'Product' model once compiled.
To fix it, add this beforeEach like you did with afterEach, so it deletes the Product model before testing again.
javascript
// server.test.js
...
beforeEach(async () => {
const url = 'mongodb://127.0.0.1:27017/test';
await mongoose.connect(url, {useNewUrlParser: true});
delete mongoose.connection.models.Product;
});
...
While we're on it, let's try to add a new product, check if it exists and clean it after. Let's also separate some describes
```javascript
// server.test.js
const request = require('supertest');
const mongoose = require('mongoose');
const app = require('./app');
const Product = require('./Product');
describe('Server', () => { describe('without acessing MongoDB', () => { afterEach(() => { app.close(); });
it('is successful', done => {
request(app)
.get('/')
.expect(200, done);
});
it('finds our stampler', done => {
request(app)
.get('/')
.expect(/Stampler/)
.expect(200, done);
});
});
describe('acessing MongoDB direcly', () => { afterEach(() => { Product.deleteOne({title: 'skate'}).exec(); });
beforeEach(async () => {
const url = 'mongodb://127.0.0.1:27017/test';
await mongoose.connect(url, {useNewUrlParser: true});
delete mongoose.connection.models.Product;
});
it('creates and finds a skate', done => {
const skate = new Product({title: 'skate'});
skate.save();
request(app)
.get('/')
.expect('Content-Type', /json/)
.expect(/skate/)
.expect(200, done);
});
}); });
```
This probably isn't the optimal and most elegant way to test integration, but I'll leave the comments open for hints and suggestions
References
Hackernoon - API testing using SuperTest Bits and Pieces- Build a Unit-Testing Suite with Mocha and Mongoose Zellwk - Connecting Jest and Mongoose SmoothTerminal - Build an API with Koa.js
Comments section