Skip to content

Instantly share code, notes, and snippets.

@WMcKibbin
Last active April 14, 2020 22:26
Show Gist options
  • Save WMcKibbin/6f8adfefbff6d2cf60d0d61ab9f6092f to your computer and use it in GitHub Desktop.
Save WMcKibbin/6f8adfefbff6d2cf60d0d61ab9f6092f to your computer and use it in GitHub Desktop.
A gist to explain mocking constructor
///////////////////////////////////////////////////////////////
//Dependency Injection
///////////////////////////////////////////////////////////////
/**
* Provides Inversion of Control interface wrapping Bottlejs
*/
class ApplicationContext {
/**
* Create a new ApplicationContext with a given name
* @param {String} name - The name of the application context
*/
constructor(name = 'main') {
if (!name) throw new Error('ApplicationContext name must be defined');
if (!typeof (name) === 'string') throw new Error('ApplicationContext name must be a string.');
try {
this._bottle = new Bottle(name);
} catch (error) {
console.error(error)
throw new Error(`Unable to create new Application Context with name ${name}`);
}
this._name = name
}
/**
* Register a new service into the Application Context. Will throw error if name|service is undefined
* @param {String} name - The name of the service that is being registered into the ApplciationContext
* @param {any} service - The service object to register to the given name.
*/
register(name, service) {
if (!name) throw new Error('No name given to register service.');
if (!typeof (name) === 'string') throw new Error('Service name must be a string.');
if (!service) throw new Error('No service object given.');
this._bottle.service(name, service);
}
/**
* Get the registered service with a given name. Throws error if name undefined or service not registered.
* @param {String} name - The name of the service to retrieve.
* @returns {any} - The Service object
*/
get(name) {
if (!name) throw new Error('Serice name must be defined')
if (!typeof (name) === 'string') throw new Error('Service name must be a string.');
const service = this._bottle.container[name];
if (!service) throw new Error(`Service ${name} not found. Please ensure all required services are registered into the Application Context`);
return service;
}
/**
* Clear the Application context of all services
*/
clear() {
Bottle.clear(this._name)
}
}
//Export Singleton Application Context to be used across the application.
const appContext = new ApplicationContext()
Object.freeze(appContext)
export default appContext;
///////////////////////////////////////////////////////////////
//User Database
///////////////////////////////////////////////////////////////
/**
* Data Access object for user profiles
*/
export default class UserDao extends Dao {
/**
* Returns a new UserDao object
* @param {Object} user - the populated user object
*/
constructor(user) {
super()
this._user = new User(user);
}
/**
* Returns a new UserDao for the user with the username specified
* @param {String} usernameDb - The unique username for the user to fetch
* @return {Promise<UserDao>} - The user with the given username
*/
static async fromUserName(usernameDb) {
if (!await Dao._init()) new Error('Database cannot be initialized');
let foundUser = await User.findOne({ 'usernameDb': usernameDb }).exec();
if (!foundUser) return null
return new UserDao(foundUser)
}
/**
* Update user contained in this UserDao
* @param {String} type - The type of update for the UserDao
* @param {String} key - The key in the UserDao to update
* @param {String|Object|Array|Boolean|Date|Number} update - The update for the UserDao
* @return {Promise<UserDao>} - The updated UserDao
*/
async updateUser(key, update, type) {
if (!await Dao._init()) new Error('Database cannot be initialized')
const updateSuccess = type ? await this._user.updateOne({[type]:{[key]: update}},{new: true}) : await this._user.updateOne({[key]: update},{new: true});
if (updateSuccess.ok === 1) return this._user.toObject()
return null
}
}
///////////////////////////////////////////////////////////////
//Testing
///////////////////////////////////////////////////////////////
/* Test Suite for Authentication Controller*/
describe("AuthController Class", function () {
//Tests for login functionality
describe("#login", function () {
before(async function () {
context.clear()
//Need to mock the user retrieval from the DB, done via UserDao Class
sinon.stub(UserDao.fromUserName, async () => {
return {
username: 'userperson',
profile: {},
password: 'bcrypt_hash_goes_here'
}
})
//This apparently does not work.
sinon.stub(UserDao.prototype.constructor, async () => {
return { updateUser: async () => { return true } } //
})
//This is an application context class that provides dependency injection for the application
// Modules grab their dependencies with context.get('service_name')
context.register('UserDao', UserDao)
})
it("login: Should throw a 400 for a password less than 8 characters", async function () {
const auth = new AuthController(process.env.SERVER_SECRET);
const shortPassword = '@1234gH'
try {
const JWT = await auth.login(testUser, shortPassword)
throw new Error('AuthController Should have thrown an 400 HttpError.')
} catch (error) {
if (!error instanceof HttpError) throw error;
expect(error.code).to.equal(400)
return true
}
});
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment