I write tests for models using Jasmine and receive the following error stack:
$ yarn test
yarn run v1.22.10
$ ENV=test db-migrate --env test up && jasmine-ts && db-migrate db:drop test
received data: /* Replace with your SQL commands */
CREATE TABLE books (
id SERIAL PRIMARY KEY,
title VARCHAR(150),
total_pages INTEGER,
author VARCHAR(255),
summary text
);
[INFO] Processed migration 20210804043247-books-table
[INFO] Done
test
Randomized with seed 85003
Started
......F.
Failures:
1) Book Model create method should add a book
Message:
Expected object to have properties
totalPages: 250
Expected object not to have properties
total_pages: 250
Stack:
Error: Expected object to have properties
totalPages: 250
Expected object not to have properties
total_pages: 250
at <Jasmine>
at UserContext.<anonymous> (/Users/chaklader/Documents/Education/Udacity/Udacity_Nano_Degree/Full_Stack_JavaScript_Developer/C_3/L_3/E_4/src/models/tests/book_spec.ts:36:24)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
8 specs, 1 failure
Finished in 0.04 seconds
Randomized with seed 85003 (jasmine --random=true --seed=85003)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
The book.ts provided below:
import client from "../database";
export type Book = {
id: number;
title: string;
author: string;
totalPages: number;
summary: string;
}
export class BookStore{
async index(): Promise<Book[]> {
try {
// #ts-ignore
const conn = await client.connect();
const sql = 'SELECT * FROM books';
const result = await conn.query(sql);
conn.release();
return result.rows;
} catch (err) {
throw new Error(`Could not get books. Error: ${err}`);
}
}
async show(id: string): Promise<Book> {
try {
const sql = 'SELECT * FROM books WHERE id=($1)'
// #ts-ignore
const conn = await client.connect()
const result = await conn.query(sql, [id])
conn.release()
return result.rows[0]
} catch (err) {
throw new Error(`Could not find book ${id}. Error: ${err}`)
}
}
async create(b: Book): Promise<Book> {
try {
const sql = 'INSERT INTO books (title, author, total_pages, summary) VALUES($1, $2, $3, $4) RETURNING *'
// #ts-ignore
const conn = await client.connect()
const result = await conn
.query(sql, [b.title, b.author, b.totalPages, b.summary])
const book = result.rows[0]
conn.release()
return book
} catch (err) {
throw new Error(`Could not add new book ${b.title}. Error: ${err}`)
}
}
}
The test file book_spec.ts is below:
import { Book, BookStore } from '../book';
const store = new BookStore();
describe('Book Model', () => {
it('should have an index method', () => {
expect(store.index).toBeDefined();
});
it('should have a show method', () => {
expect(store.show).toBeDefined();
});
it('should have a create method', () => {
expect(store.create).toBeDefined();
});
it('should have a update method', () => {
expect(store.create).toBeDefined();
});
it('should have a delete method', () => {
expect(store.delete).toBeDefined();
});
it('create method should add a book', async () => {
const result = await store.create({
id: 1,
title: 'Bridge to Terabithia',
author: 'Katherine Paterson',
totalPages: 250,
summary: 'Childrens',
});
expect(result).toEqual({
id: 1,
title: 'Bridge to Terabithia',
totalPages: 250,
author: 'Katherine Paterson',
summary: 'Childrens',
});
});
});
The data is populated properly in the bd:
The SQL statement in the bd migration is provided:
CREATE TABLE books (
id SERIAL PRIMARY KEY,
title VARCHAR(150),
total_pages INTEGER,
author VARCHAR(255),
summary text
);
What's the issue here and how do I correct it?
Your Book has camelCase properties but your books table has snake_case properties. You either need to pick one type of casing for both or when you get the result from the database create a new object that is of type Book.
const book = {
id: result.rows[0].id,
title: result.rows[0].title,
author: result.rows[0].author,
totalPages: result.rows[0].total_pages,
summary: result.rows[0].summary
}
Another option is to use something like lodash.camelcase and map the object keys to convert them.
Related
I have the following arrangement of tests using sinon, mocha and chai:
type ModelObject = {
name: string;
model: typeof Categoria | typeof Articulo | typeof Usuario;
fakeMultiple: () => object[];
fakeOne: (id?: string) => object;
}
const models: ModelObject[] = [
{
name: 'categorias',
model: Categoria,
fakeMultiple: () => fakeMultiple({ creator: oneCategoria }),
fakeOne: oneCategoria
},
{
name: 'articulos',
model: Articulo,
fakeMultiple: () => fakeMultiple({ creator: oneArticulo }),
fakeOne: oneArticulo
},
{
name: 'usuarios',
model: Usuario,
fakeMultiple: () => fakeMultiple({ creator: oneUsuario }),
fakeOne: oneUsuario
}
];
const randomModel = models[Math.floor(Math.random() * models.length)];
describe(`v1/${randomModel.name}`, function () {
this.afterEach(function () {
sinon.restore();
});
context.only("When requesting information from an endpoint, this should take the Model of the requested endpoint and query the database for all the elements of that model", function () {
it.only(`Should return a list of elements of ${randomModel.name} model`, function (done) {
const fakes = randomModel.fakeMultiple();
const findFake = sinon.fake.resolves({ [randomModel.name]: fakes });
sinon.replace(randomModel.model, 'find', findFake);
chai.request(app)
.get(`/api/v1/${randomModel.name}`)
.end(
(err, res) => {
expect(res).to.have.status(200);
expect(res.body.data).to.be.an('object');
expect(res.body.data).to.have.property(randomModel.name);
expect(res.body.data[randomModel.name]).to.have.lengthOf(fakes.length);
expect(findFake.calledOnce).to.be.true;
done();
}
)
});
}}
I use this to test an endpoint that arbitrary returns information about a given model. In my controllers, I'm using a dynamic middleware to determine which model is going to be queried, for example, if the route consumed is "api/v1/categorias", it will query for Categorias model. If the route consumed is "api/v1/articulos", it will query for Articulos model, and so on.
To make the query, i use the following service:
import { Articulo } from '../models/articulo';
import { Usuario } from '../models/usuario';
import { Categoria } from '../models/categoria';
import logger from '../config/logging';
import { Model } from 'mongoose';
const determineModel = (model: string): Model<any> => {
switch (model) {
case 'articulos':
return Articulo;
case 'usuarios':
return Usuario;
case 'categorias':
return Categoria;
default:
throw new Error(`Model ${model} not found`);
}
};
export const getInformation = async (schema: string, page: number, limit: number) => {
try {
const model = determineModel(schema);
const data = await model.find().skip((page - 1) * limit).limit(limit);
const dataLength = await model.find().countDocuments();
return {
data,
total: dataLength,
};
} catch (err) {
logger.error(err);
console.log(err);
throw err;
}
};
The problem here lies when running my tests, it seems that is unable to run the .skip() and .limit() methods for my model.find()
error: model.find(...).skip is not a function
TypeError: model.find(...).skip is not a function
I think that I need to fake those methods, because when running the same test without skip and limit, it works as a charm. My problem lies in the fact that I don't know how to fake those, or to see if my guess is correct.
As a note, I have default params for the variables page and limit (1 and 15 respectively) so I'm not passing empty values to the methods.
I have the below line in my service.
const timeline = await buildClient.getBuildTimeline(project.id, buildId);
The above code is successfully mocked by jest as below using the __mocks__ approach.
jest.mock('azure-devops-extension-api', () => {
return { getClient: (client: any) => new GitRestClient() };
});
export const mockGetItems = jest
.fn()
.mockReturnValue({ records: [{ state: 2, type: 'TASK', task: { id: TASK_IDS[0] } }] });
class GitRestClient {
public getBuildTimeline(projectId: string, buildId: string): Promise<Timeline[]> {
return new Promise((resolve) => resolve(mockGetItems()));
}
}
But when I get error during jest test in the below find method which acts upon the mocked response.
const record = timeline.records.find((rec) => {
return rec.type === 'Task';
});
How should I update so that the above code snippet is mocked perfectly to return the record value using the find method.
Use case:
I am trying to insert a record inside the amazon QLDB using Node and typescript.
I am able to insert the record/document successfully and it returns me documentID in return.
there are 2 controllers: EntityController and CommonController
-EntityController extends CommonController
-EntityController has the code for getting req object converting it into the model object and the calling insert() function that has been extended from the CommonController.
problem
I am trying to propagate that documentID to all the way to my API call, but somehow I am getting undefined in the EntityController.
whereas I am able to print the documentID in CommonController.
I am not sure why I am getting undefined when I am clearly returning a value.
const CommonController = require("../template/controller");
import { Request, Response } from 'express';
const tableName:string = "entities";
const EntityModel = require("./model")
class EntityController extends CommonController {
async insertEntitiy(req:Request,res:Response) {
async insertEntitiy(req:any,res:any) {
console.log(req);
console.log("===========");
console.log(req.body);
let entity = new EntityModel();
entity.balance = req.body.balance;
entity.firstName = req.body.firstName;
entity.lastName = req.body.lastName;
entity.email = req.body.email;
try {
let documentIds = await this.insert(tableName,entity);
console.log("--------- inside insertEntity fiunction()---------");
console.log(documentIds);
console.log("------------------");
res.status(200).send(documentIds[0]);
} catch (error) {
console.error(`error in creating Entity: ${error}`);
res.status(500).send({ errMsg: `error in creating Entity: ${error}` });
}
}
}
module.exports = new EntityController();
import { createQldbWriter, QldbSession, QldbWriter, Result, TransactionExecutor } from "amazon-qldb-driver-nodejs";
import { Reader } from "ion-js";
import { error, log } from "../qldb/LogUtil";
import { getFieldValue, writeValueAsIon } from "../qldb/Util";
import { closeQldbSession, createQldbSession } from "../qldb/ConnectToLedger";
module.exports = class Conroller {
async insert(tablename:string, object:any): Promise<Array<string>> {
let session: QldbSession;
let result:Array<string>;
try {
session = await createQldbSession();
await session.executeLambda(async (txn) => {
result = await this.insertDocument(txn,tablename,object);
console.log("---------result inside insert fiunction()---------");
console.log(result);
console.log("------------------");
return (Promise.resolve(result));
})
} catch (e) {
error(`Unable to insert documents: ${e}`);
return(["Error"]);
} finally {
closeQldbSession(session);
}
}
/**
* Insert the given list of documents into a table in a single transaction.
* #param txn The {#linkcode TransactionExecutor} for lambda execute.
* #param tableName Name of the table to insert documents into.
* #param documents List of documents to insert.
* #returns Promise which fulfills with a {#linkcode Result} object.
*/
async insertDocument(
txn: TransactionExecutor,
tableName: string,
documents: object
): Promise<Array<string>> {
const statement: string = `INSERT INTO ${tableName} ?`;
const documentsWriter: QldbWriter = createQldbWriter();
let documentIds: Array<string> = [];
writeValueAsIon(documents, documentsWriter);
let result: Result = await txn.executeInline(statement, [documentsWriter]);
const listOfDocumentIds: Reader[] = result.getResultList();
listOfDocumentIds.forEach((reader: Reader, i: number) => {
documentIds.push(getFieldValue(reader, ["documentId"]));
});
console.log("---------documentIds---------");
console.log(documentIds);
console.log("------------------");
return (documentIds);
}
}
ouptut :
---------documentIds---------
[ '4o5UZjMqEdgENqbP9l7Uhz' ]
---------result inside insert fiunction()---------
[ '4o5UZjMqEdgENqbP9l7Uhz' ]
--------- inside insertEntity fiunction()---------
undefined
As #daniel-w-strimpel pointed out in the comments, your insert method returns only in the catch part.
Try this:
insert(tablename:string, object:any): Promise<Array<string>> {
let session: QldbSession;
let result: Array<string>;
try {
session = await createQldbSession();
return session.executeLambda(async (txn) => {
result = await this.insertDocument(txn,tablename,object);
console.log("---------result inside insert fiunction()---------");
console.log(result);
console.log("------------------");
return result;
})
} catch (e) {
error(`Unable to insert documents: ${e}`);
return(["Error"]);
} finally {
closeQldbSession(session);
}
}
...
In return session.executeLambda you return the Promise.
In return result; you return the actual value.
More on promises here: https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
I wasn't able to make unit testing worked using jest
I'm trying to test a specific function that's calling or expecting result from other function but I'm not sure why it is not working. I'm pretty new to unit testing and really have no idea how could I make it work. currently this is what I've tried
export class OrganizationService {
constructor() {
this.OrganizationRepo = new OrganizationRepository()
}
async getOrganizations(req) {
if (req.permission !== 'internal' && req.isAuthInternal === false) {
throw new Error('Unauthenticated')
}
const opt = { deleted: true }
return this.OrganizationRepo.listAll(opt)
}
}
This is my OrganizationRepository that extends the MongoDbRepo
import { MongoDbRepo } from './mongodb_repository'
export class OrganizationRepository extends MongoDbRepo {
constructor(collection = 'organizations') {
super(collection)
}
}
and this is the MongoDbRepo
const mongoClient = require('../config/mongo_db_connection')
const mongoDb = require('mongodb')
export class MongoDbRepo {
constructor(collectionName) {
this.collection = mongoClient.get().collection(collectionName)
}
listAll(opt) {
return new Promise((resolve, reject) => {
this.collection.find(opt).toArray((err, data) => {
if (err) {
reject(err)
}
resolve(data)
})
})
}
}
and this is the test that I've made
import { OrganizationService } from '../../../src/services/organization_service'
describe('getOrganizations', () => {
it('should return the list of organizations', () => {
// const OrgRepo = new OrganizationRepository()
const orgService = new OrganizationService()
const OrgRepo = jest.fn().mockReturnValue("[{_id: '123', name: 'testname'}, {_id: '456, name: 'testname2'}]")
// orgService.getOrganizations = jest.fn().mockReturnValue('')
const result = orgService.getOrganizations()
expect(result).toBe(OrgRepo)
})
})
I see two issues in the way you are testing:
1.
You are trying to test an asynchronous method, and on your test, you are not waiting for this method to be finished before your expect statement.
A good test structure should be:
it('should test your method', (done) => {
const orgService = new OrganizationService();
const OrgRepo = jest.fn().mockReturnValue("[{_id: '123', name: 'testname'}, {_id: '456, name: 'testname2'}]")
orgService.getOrganizations()
.then((result) => {
expect(result).toEqual(OrgRepo); // I recommend using "toEqual" when comparing arrays
done();
});
})
Don't forget to put done as a parameter for your test!
You can find more about testing asynchronous functions on the Jest official documentation.
2.
In order to test your method properly, you want to isolate it from external dependencies. Here, the actual method OrganizationRepo.listAll() is called. You want to mock this method, for instance with a spy, so that you control its result and only test the getOrganizations method. That would look like this:
it('should test your method', (done) => {
const req = {
// Whatever structure it needs to be sure that the error in your method is not thrown
};
const orgService = new OrganizationService();
const orgRepoMock = spyOn(orgService['OrganizationRepo'], 'listAll')
.and.returnValue(Promise.resolve("[{_id: '123', name: 'testname'}, {_id: '456, name: 'testname2'}]"));
const OrgRepo = jest.fn().mockReturnValue("[{_id: '123', name: 'testname'}, {_id: '456, name: 'testname2'}]");
orgService.getOrganizations(req)
.then((result) => {
expect(result).toEqual(OrgRepo); // I recommend using "toEqual" when comparing arrays
expect(orgRepoMock).toHaveBeenCalled(); // For good measure
done();
});
})
This way, we make sure that your method is isolated and its outcome cannot be altered by external methods.
For this particular method, I also recommend that you test the error throwing depending on the input of your method.
I was able to answer this, first is I mocked the repository using
jest.mock('path/to/repo')
const mockGetOne = jest.fn()
OrganizationRepository.protorype.getOne = mockGetOne
then the rest is the test
I am trying to understand why I'm getting a TypeError: ____.andWhereRaw is not a function using ObjectionJs with Postgres and how to resolve this error.
const { Model } = require('objection')
// Website Table
exports.up (knex, Promise) => {
return knex.schema.createTable('web', table => {
table.string('id');
table.string('website');
table.jsonb('other_sites').defaultTo('[]');
}
}
exports.down .... // blah blah blah.....
*****************************************************************
// Website data:
{
'id': 'orgIdA',
'website': 'site1.com',
'other_sites': ['foo.com', 'bar.com', 'xyz.com']
}
Class Website extends Model {
const results = await GetModel // GetModel is a function "references"
.query()
.where('id', organizationId)
.andWhere('website', website)
const otherSite = results.map(site=> site= {...account.otherSites});
if (otherSite) {
Object.keys(otherSite).forEach(key => {
results.andWhereRaw(`other_sites #> '{"${key}": "foo.com"}'`)
});
}
}
TypeError: results.andWhereRaw is not a function