Skip to content

Instantly share code, notes, and snippets.

@aukgit
Last active March 25, 2020 13:14
Show Gist options
  • Save aukgit/b382b9288e73e88f73299aa7f27e99f2 to your computer and use it in GitHub Desktop.
Save aukgit/b382b9288e73e88f73299aa7f27e99f2 to your computer and use it in GitHub Desktop.
Jest-Sample-Tests-By-Md-AlimUlKarim-aukgit

Jest-Sample-Test by Md. Alim Ul Karim

Video (Demo)

Links

Task : 01 (Deep Cloning)

Write a function called deepClone which takes an object and creates a copy of it. e.g. {name: "Paddy", address: {town: "Lerum", country: "Sweden"}} -> {name: "Paddy", address: {town: "Lerum", country: "Sweden"}}

Task : 01 (Solution)

Notes: Deep cloner takes any object and verifies if it is not a string, function or number then the given object is a valid one then converts to JSON string and then converts that string back to object returns a total deep clone of the object given.

Task : 02 (Coordinates nearest for quick meal)

We'd like to contact partners with offices within 100km of central London (coordinates 51.515419, -0.141099) to invite them out for a meal.

Write a NodeJS/JavaScript program that reads our list of partners (use the attached JSON partners.zip or http://bit.ly/2wQaLyU) and outputs the company names and addresses of matching partners (with offices within 100km) sorted by company name (ascending).

You can use the first formula from this Wikipedia article (https://en.wikipedia.org/wiki/Great-circle_distance) to calculate distance. Don't forget to convert degrees to radians! Your program should be fully tested too.

Task : 02 (Solution)

Code Coverage Screenshots

image

image

Coverage Reports

https://drive.google.com/drive/folders/1HJv55cLCE-b2RyCYjJhuGLk6HCstn-lj?usp=sharing

Note: After running watch coverage reports will be found in the coverage folder of the root of the projects.

Coverage Logs

Watch Usage: Press w to show more.
 PASS  coordinates/__tests__/coordinate.spec.js
  Coordinate class tests
    [Constructor Testing] Coordinate.constructor(string)
      √ [Invlaid] Input(null->null) -> Coordinate.constructor(string) throws error. (2ms)
      √ [Invlaid] Input(undefined->undefined) -> Coordinate.constructor(string) throws error. (1ms)
      √ [Invlaid] Input(1->AnyNumber) -> Coordinate.constructor(string) throws error.
      √ [Invlaid] Input({}->{}(AnyObject)) -> Coordinate.constructor(string) throws error. (1ms)
      √ [Invlaid] Input("x,1"->x,1) -> Coordinate.constructor(string) throws error.
      √ [Invlaid] Input("x, 1"->x, 1) -> Coordinate.constructor(string) throws error.
      √ [Invlaid] Input("1, x"->1,x) -> Coordinate.constructor(string) throws error. (1ms)
      √ [Invlaid] Input("x,y"->x,y) -> Coordinate.constructor(string) throws error.
      √ [Invlaid] Input("1.5,y"->1.5,y) -> Coordinate.constructor(string) throws error.
      √ [Invlaid] String input("any thing which doesn't have coma") -> Coordinate.constructor(string) keeps isValid flag to false. (1ms)
      √ [Valid] String input(""1,2"") -> Coordinate.constructor(string) keeps isValid flag to true.
      √ [Valid] String input(""5.1,2.5"") -> Coordinate.constructor(string) keeps isValid flag to true.
      √ [Valid] String input(""5.1, 2.5"") -> Coordinate.constructor(string) keeps isValid flag to true. (1ms)
    Coordinate.throwIfInvalid()
      √ [Integration] Throws if flag is not valid.
    Coordinate.toString()
      √ [Integration] Coordinate("1, 2").toString() returns given cordinates string ("1, 2").
    Coordinate.getDistanceOf(anotherCoordinate) || Coordinate.throwIfInvalid()
      √ [Invalid] "getDistanceOf"() -> throws if flag is not valid. (1ms)
      √ [Invalid] "throwIfInvalid"() -> throws if flag is not valid.
      √ [Error/Exception Testing] getDistanceOf(null) -> throws if flag is not valid.
      √ [Error/Exception Testing] getDistanceOf(undefined) -> throws if flag is not valid.
      √ [Error/Exception Testing] getDistanceOf({"coordinates": "51.515419,-0.141099", "coordinatesArray": [Array], "isValid": true, "throwIfInvalid": undefined, "x": 51.515419, "y": -0.141099}) -> throws if flag is not valid.
      √ [Mutation Verification] .throwIfInvalid() -> , converterUtility.getDistance() same one return 0 distance. (2ms)

 PASS  coordinates/__tests__/partnersProfile.spec.js
  PartnersProfile class tests
    √ [Integration] idealCoordinate should be "51.515419,-0.141099" (1ms)
    √ [Integration] .readFileUsingRequire() throws exception if invalid path given." (15ms)
    [Constructor Testing] .constructor(string)
      √ [Invlaid] Input(null -> null) -> PartnersProfile.constructor(string) throws error. (2ms)
      √ [Invlaid] Input(undefined -> undefined) -> PartnersProfile.constructor(string) throws error.
      √ [Invlaid] Input(1 -> AnyNumber) -> PartnersProfile.constructor(string) throws error.
      √ [Invlaid] Input({} -> {}(AnyObject)) -> PartnersProfile.constructor(string) throws error. (1ms)
      √ [Invlaid] String('anystring,$$##@') PartnersProfile.constructor(string -> meaning giving invalid path string) -> doesn't throw any error.
    .debugPrintPartnerProfilesWithinHundredKilometersOrderByCompanyName() displays
      √ When (null), prints "No records found within range." (2ms)
      √ When (undefined), prints "No records found within range."
      √ When ([]), prints "No records found within range." (1ms)
      √ When (["Hello World1"]), prints "Hello World1"
    .partnerProfileOrderByCompanyNameAscending() ascending sorting (f.organization - l.organization)
      √ [Invalid] When ([null, null]), prints "Given profile is either invalid or doesn' have "organization" property." (1ms)
      √ [Invalid] When ([undefined, undefined]), prints "Given profile is either invalid or doesn' have "organization" property."
      √ [Invalid] When ([[Object], undefined]), prints "Given profile is either invalid or doesn' have "organization" property." (1ms)
      √ [Invalid] When ([[Object], [Object]]), prints "Given profile is either invalid or doesn' have "organization" property."
      √ [Integration, Valid] When (organization:5 - organization:3) returns 2.
    .getPartnerProfilesWithinHundredKilometersOrderByCompanyNamesDisplay() gets filtered profiles display array.
      √ [Mutation] getQuickestDistanseProfilesWithinHundredKilometers() must be called at once. (1ms)
      √ [Integration] creates display string.
    .getQuickestDistanseProfilesWithinHundredKilometers() gets filtered profiles.
      √ [Mutation] getPartnerProfiles() must be called at once. (1ms)
      √ [Mutation] filterPartnerProfileWithinHundredKilometers() must be called at once for filtering.
    .isCurrentAddressWithinHundredKilomitersRangeExceptDropAddress(partnerProfile, officeIndex) returns true if within 100Km range and mutates or removes office address from array if not within range.
      √ [Within Range] Is within 100km range, adds distance and don't remove from office address. (1ms)
      √ [NOT Within Range] Is NOT within 100km range (> 100KM), removes address from the offices array.
    .getPartnerProfiles() reads profile json data file from file system.
      √ [Using Mock] Throws error if given path is not exist in the system. (1ms)
      √ [Integration] Throws error if given path is not exist in the system.
      √ [Valid Path, Using Mock] Returns JSON data.
      √ [Valid Path, Using Mock] Returns null and console log while have error during read. (1ms)
    .filterPartnerProfileWithinHundredKilometers() returns true if any address is within range of 100km from IdealCoordinate(51.515419,-0.141099).
      √ [Invalid, Integration] Returns false 'office' property doesn't exist or 'office'(undefined/null/[]) or profile is undefined/null.
      √ [Valid, Using Mock] If office address coordinate is not within range then returns false.
      √ [Valid Input, Using Mock] If office (any) address coordinate is not within range then returns 'true'.

 PASS  utilities/__tests__/converterUtility.spec.js
  ConverterUtility class tests.
    .convertDegreesToRadians(degree) converts given number as degrees to radians
      √ [Integration] .convertDegreesToRadians(0) should return 0 (1ms)
      √ [Integration] .convertDegreesToRadians(30) should return 0.5235987756
      √ [Integration] .convertDegreesToRadians(120) should return 2.0943951024
      √ [Integration] .convertDegreesToRadians(360) should return 6.2831853072
    .getDistance(lat1, long1, lat2, long2) gives a distance in between coordinates.
      √ [Integration] .getDistance(
                        lat1:53.32055555555556,
                        long1: -1.7297222222222222,
                        lat2: 53.31861111111111,
                        long2:-1.6997222222222224) -> should return '2.0043678382716137' (1ms)
      √ [Mutation with Mock] .getDistance() should call it's inner method with expected call numbers (1ms)

 PASS  cloner/__tests__/deepCloner.spec.js
  deep copy/cloning
    DeepCloner.objectToJsonString(object) takes object and converts to string
      √ [Exist function with 1 parameter] '.objectToJsonString(arg)' exist and has one argumnet. (2ms)
      √ [Invalid] Input(null->null) should throw Error with message "Invalid type null/undefined/function/string/number." (1ms)
      √ [Invalid] Input(undefined->undefined) should throw Error with message "Invalid type null/undefined/function/string/number."
      √ [Invalid] Input(1->1) should throw Error with message "Invalid type null/undefined/function/string/number." (1ms)
      √ [Invalid] Input(""->EmptyString("" or '')) should throw Error with message "Invalid type null/undefined/function/string/number."
      √ [Invalid] Input("validString"->validString) should throw Error with message "Invalid type null/undefined/function/string/number."
      √ [Invalid] Input([Function objectToJsonString]->function f() {...}) should throw Error with message "Invalid type null/undefined/function/string/number." (1ms)
      √ [Valid] Input({"id": null}) should return "{"id":null}"
      √ [Valid] Input({"id": ""}) should return "{"id":""}"
      √ [Valid] Input({}) should return "{}"
      √ [Valid] Input([]) should return "[]"
      √ [Valid] Input([[Object], [Object]]) should return "[{"id":null},{}]"
    DeepCloner.deepClone(object) takes object and converts to JSON and then converts that to object (deep cloning).
      √ [Exist function with 1 parameter] '.deepClone(arg)' exist and has one argumnet.
      √ [Mocked, Mutation] '.deepClone(arg)' invokes DeepCloner.objectToJsonString(object) at once. (3ms)
      √ [Integration] '.deepClone(arg)' deep clones to nth level, at least 4th level json is cloned properly tested. (1ms)

----------------------|---------|----------|---------|---------|-------------------
File                  | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------------------|---------|----------|---------|---------|-------------------
All files             |     100 |    98.53 |     100 |     100 |
 cloner               |     100 |      100 |     100 |     100 |
  deepCloner.js       |     100 |      100 |     100 |     100 |
 coordinates          |     100 |    98.25 |     100 |     100 |
  coordinate.js       |     100 |      100 |     100 |     100 |
  partnersProfile.js  |     100 |     97.3 |     100 |     100 | 112
 utilities            |     100 |      100 |     100 |     100 |
  converterUtility.js |     100 |      100 |     100 |     100 |
----------------------|---------|----------|---------|---------|-------------------
Test Suites: 4 passed, 4 total
Tests:       71 passed, 71 total
Snapshots:   0 total
Time:        2.188s
Ran all test suites.

Notes: Reads the json file given and then filters out the office address which are not in range. Only those offices has a valid office address within range of 100km will be displayed.

Implementation Screenshots

image

PS D:\PersonalWork\Github\Jest-Sample-Test> node app
Orginal Object:
{ name: 'Paddy', address: { town: 'Lerum', country: 'Sweden' } }
Cloned Modified Object:
{ name: 'Alim',
  address: { town: 'my Town', country: 'Sweden' } }
-------------


-------------
reading partners from: D:\PersonalWork\Github\Jest-Sample-Test\data\partners.json
 Company/Organization Name: "Blue Square 360",
 --- Address: "St Saviours Wharf, London SE1 2BE",
 ---- (lat, long) = (51.5014767,-0.0713608999999451), Distance: 5.069289250892033
 Company/Organization Name: "Gallus Consulting",
 --- Address: "Newton House, Northampton Science Park, Moulton Park, Kings Park Road, Northampton, NN3 6LG",
 ---- (lat, long) = (52.277409,-0.877935999999977), Distance: 98.66650647724738
 Company/Organization Name: "Gallus Consulting",
 --- Address: "No1 Royal Exchange, London, EC3V 3DG",
 ---- (lat, long) = (51.5136102,-0.08757919999993646), Distance: 3.708943084465606

Running Commands

  • node run dev-all will start development with watch.
  • node app will start and run the application.

Contact

const cloningUtility = require('./utilities/cloningUtility').CloningUtility;
cloningUtility.performClone();
console.log('-------------')
console.log('')
console.log('')
console.log('-------------')
const path = require( "path" );
var partnersProfileJSONPath = path.resolve("./data/partners.json");
console.log('reading partners from: ' + partnersProfileJSONPath);
const PartnersProfile = require('./coordinates/partnersProfile').PartnersProfile;
const partnersProfileStreamer = new PartnersProfile(partnersProfileJSONPath);
partnersProfileStreamer.debugPrintPartnerProfilesWithinHundredKilometersOrderByCompanyName();
var deepCloner = require('../cloner/deepCloner').DeepCloner;
class CloningUtility {
/**
* return a object
* {
* name: 'Paddy',
* address: {
* town: 'Lerum',
* country: 'Sweden'
* }
* }
*/
static genericPerson() {
return {
name: 'Paddy',
address: {
town: 'Lerum',
country: 'Sweden'
}
};
}
/**
* perform deep cloning and logs to console.
*/
static performClone() {
const person = this.genericPerson();
let clonedObject = deepCloner.deepClone(person);
clonedObject.address.town = 'my Town';
clonedObject.name = 'Alim';
console.log('Orginal Object:');
console.log(person);
console.log('Cloned Modified Object:');
console.log(clonedObject);
}
}
module.exports = { CloningUtility };
class ConverterUtility {
/**
* Convert degrees to radians
* degree * PI / 180
* @param {number} deg
*/
static convertDegreesToRadians(deg) {
if (!deg || isNaN(deg)) {
return 0;
}
return parseFloat(deg) * Math.PI / 180;
}
/**
* Reference : https://en.wikipedia.org/wiki/Great-circle_distance
* Reference 2 : https://www.geeksforgeeks.org/program-distance-two-points-earth/
* @param {*} lat1 (in degrees)
* @param {*} long1 (in degrees)
* @param {*} lat2 (in degrees)
* @param {*} long2 (in degrees)
*/
static getDistance(lat1, long1, lat2, long2) {
const R = 6371;
lat1 = this.convertDegreesToRadians(lat1);
long1 = this.convertDegreesToRadians(long1);
lat2 = this.convertDegreesToRadians(lat2);
long2 = this.convertDegreesToRadians(long2);
const dlong = long2 - long1;
const dlat = lat2 - lat1;
const difference = Math.pow(Math.sin(dlat / 2), 2) +
Math.cos(lat1) * Math.cos(lat2) *
Math.pow(Math.sin(dlong / 2), 2);
const distance = 2 * Math.asin(Math.sqrt(difference));
// Radius of Earth in
// Kilometers, R = 6371
// Use R = 3956 for miles
// Calculate the result
const finalResult = distance * R;
return finalResult;
}
}
module.exports = { ConverterUtility };
const converterUtility = require('../converterUtility').ConverterUtility;
const expectedDigitsToMatch = 5;
describe('ConverterUtility class tests.', () => {
/**
* See reference : https://www.rapidtables.com/convert/number/degrees-to-radians.html
*/
describe('.convertDegreesToRadians(degree) converts given number as degrees to radians', () => {
const testCases = [
[0, 0],
[30, 0.5235987756],
[120, 2.0943951024],
[360, 6.2831853072]
]
test.each(testCases)('[Integration] .convertDegreesToRadians(%s) should return %s', (input, expected) => {
// Act
const actual = converterUtility.convertDegreesToRadians(input);
// Assert
expect(actual).toBeCloseTo(expected, expectedDigitsToMatch);
});
});
/**
* Reference : https://en.wikipedia.org/wiki/Great-circle_distance
* Reference 2 : https://www.geeksforgeeks.org/program-distance-two-points-earth/
*
*/
describe('.getDistance(lat1, long1, lat2, long2) gives a distance in between coordinates.', () => {
const testCases = [
[
53.32055555555556,
-1.7297222222222221,
53.31861111111111,
-1.6997222222222223,
2.0043678382716137 // expected
]
]
const parameterIndenting = '\n\t\t\t';
/**
* Input : Latitude 1: 53.32055555555556
* Longitude 1: -1.7297222222222221
* Latitude 2: 53.31861111111111
* Longitude 2: -1.6997222222222223
* Output: Distance is: 2.0043678382716137 Kilometers
*/
test.each(testCases)(`[Integration] .getDistance(${parameterIndenting}lat1:%s, ${parameterIndenting}long1: %s, ${parameterIndenting}lat2: %s, ${parameterIndenting}long2:%s) -> should return '%s'`, (lat1, long1, lat2, long2, expected) => {
// Act
const actual = converterUtility.getDistance(lat1, long1, lat2, long2);
// Assert
expect(actual).toBeCloseTo(expected, expectedDigitsToMatch);
});
test('[Mutation with Mock] .getDistance() should call it\'s inner method with expected call numbers', () => {
// Arrange
const radiansConverterMock = jest.spyOn(converterUtility, converterUtility.convertDegreesToRadians.name);
radiansConverterMock.mockImplementation(_ => 5);
const expectedCalls = 4;
// Act
const actual = converterUtility.getDistance(1, 1, 1, 1);
// Assert
expect(radiansConverterMock).toHaveBeenCalledTimes(expectedCalls);
expect(actual).toBe(0);
});
});
});
const converterUtility = require('../utilities/converterUtility').ConverterUtility;
class Coordinate {
constructor(coordinates) {
if (!coordinates || typeof (coordinates) !== 'string') {
throw new Error('Given cordinate is not valid.');
}
this.coordinates = coordinates;
const splitter = ',';
if (coordinates.indexOf(splitter) < 0) {
this.isValid = false;
return;
}
this.coordinatesArray = coordinates.split(splitter);
this.x = this.coordinatesArray[0];
this.y = this.coordinatesArray[1];
if (isNaN(this.x) || isNaN(this.y) || !this.x || !this.y) {
throw new Error('Given cordinate is not valid.');
}
this.x = parseFloat(this.x.trim());
this.y = parseFloat(this.y.trim());
this.isValid = true;
}
throwIfInvalid() {
if (!this.isValid) {
const errorMessage =
'Current cordinates(' + this.coordinates + ') are not valid. Value: ' + this.coordinates;
throw new Error(errorMessage);
}
}
getDistanceOf(anotherCoordinate) {
this.throwIfInvalid();
if (!anotherCoordinate || !anotherCoordinate.throwIfInvalid) {
throw new Error('Given cordinates are not valid.');
}
anotherCoordinate.throwIfInvalid();
if(this === anotherCoordinate){
return 0;
}
return converterUtility.getDistance(
this.x,
this.y,
anotherCoordinate.x,
anotherCoordinate.y);
}
toString() {
return this.coordinates;
}
}
module.exports = { Coordinate };
const Coordinate = require('../coordinate').Coordinate;
const idealCoordinate = require('../partnersProfile').IdealCoordinate;
describe('Coordinate class tests', () => {
describe('[Constructor Testing] Coordinate.constructor(string)', () => {
// Arrange (Global for this method only)
const errorMessage = 'Given cordinate is not valid.';
const invalidTestCases = [
[null, null],
[undefined, undefined],
[1, 'AnyNumber'],
[{}, '{}(AnyObject)'],
['x,1', 'x,1'],
['x, 1', 'x, 1'],
['1, x', '1,x'],
['x,y', 'x,y'],
['1.5,y', '1.5,y'],
];
test.each(invalidTestCases)('[Invlaid] Input(%p->%s) -> Coordinate.constructor(string) throws error.', (input) => {
// Arrange
let actualThrownError = null;
let createdCoordinate;
// Act
try {
createdCoordinate = new Coordinate(input);
} catch (error) {
actualThrownError = error;
}
// Assert
expect(createdCoordinate).not.toBeNull();
expect(actualThrownError).not.toBeNull();
expect(actualThrownError.message).toBe(errorMessage);
});
test('[Invlaid] String input("any thing which doesn\'t have coma") -> Coordinate.constructor(string) keeps isValid flag to false.', () => {
// Arrange
const input = 'anystring';
// Act
const actual = new Coordinate(input);
// Assert
expect(actual.isValid).toBeFalsy();
});
const validTestCases = [
['1,2'],
['5.1,2.5'],
['5.1, 2.5']
];
test.each(validTestCases)('[Valid] String input("%p") -> Coordinate.constructor(string) keeps isValid flag to true.', (input) => {
// Arrange
const splitArray = input.split(',');
const first = parseFloat(splitArray[0].trim());
const second = parseFloat(splitArray[1].trim());
// Act
const actual = new Coordinate(input);
// Assert
expect(actual.isValid).toBeTruthy();
expect(actual.x).toBe(first);
expect(actual.y).toBe(second);
});
});
describe('Coordinate.throwIfInvalid()', () => {
test('[Integration] Throws if flag is not valid.', () => {
// Arrange
const input = 'anystring';
let actualThrownError;
const errorMessage =
`Current cordinates(${input}) are not valid. Value: ${input}`;
// Act
try {
const actual = new Coordinate(input);
actual.throwIfInvalid();
} catch (error) {
actualThrownError = error;
}
// Assert
expect(actualThrownError).not.toBeNull();
expect(actualThrownError.message).toBe(errorMessage);
});
});
describe('Coordinate.toString()', () => {
test('[Integration] Coordinate("1, 2").toString() returns given cordinates string ("1, 2").', () => {
// Arrange
const input = '1, 2';
// Act
const actual = new Coordinate(input);
// Assert
expect(actual.toString()).toBe(input);
});
});
describe('Coordinate.getDistanceOf(anotherCoordinate) || Coordinate.throwIfInvalid()', () => {
const methodsTotest = [
'getDistanceOf',
'throwIfInvalid'
];
test.each(methodsTotest)('[Invalid] %p() -> throws if flag is not valid.', (methodName) => {
// Arrange
const input = 'anystring';
let actualThrownError;
const errorMessage =
`Current cordinates(${input}) are not valid. Value: ${input}`;
// Act
try {
const actual = new Coordinate(input);
actual[methodName]();
} catch (error) {
actualThrownError = error;
}
// Assert
expect(actualThrownError).not.toBeNull();
expect(actualThrownError.message).toBe(errorMessage);
});
// Test Cases
let anotherCoordinate = new Coordinate(idealCoordinate.coordinates);
anotherCoordinate.throwIfInvalid = undefined;
const inValidTestCasesForGetDistanceOf = [
null,
undefined,
anotherCoordinate,
];
test.each(inValidTestCasesForGetDistanceOf)('[Error/Exception Testing] getDistanceOf(%p) -> throws if flag is not valid.', (input) => {
// Arrange
let actualThrownError;
const errorMessage = 'Given cordinates are not valid.';
// Act
try {
const actual = new Coordinate(idealCoordinate.coordinates);
actual.getDistanceOf(input);
} catch (error) {
actualThrownError = error;
}
// Assert
expect(actualThrownError).not.toBeNull();
expect(actualThrownError.message).toBe(errorMessage);
});
test('[Mutation Verification] .throwIfInvalid() -> , converterUtility.getDistance() same one return 0 distance.', () => {
// Arrange
const input = '1, 2';
const input2 = '1, 3';
const expected = 0;
const instance = new Coordinate(input);
const anotherInstance = new Coordinate(input2);
const throwIfInValidMock = jest.spyOn(instance, 'throwIfInvalid');
throwIfInValidMock.mockImplementation(_ => { });
// same.getDistanceOf(same) -> 2, another.getDistanceOf(same) -> 1
// total = 3
const expectedCallsForthrowIfInValidMock = 3;
const expectedCallsForgetDistanceUtilityMock = 1;
const converterUtility = require('../../utilities/converterUtility').ConverterUtility;
const getDistanceUtilityMock = jest.spyOn(converterUtility, converterUtility.getDistance.name);
getDistanceUtilityMock.mockImplementation(_ => 0);
// Act
const actual = instance.getDistanceOf(instance);
const actualSecond = anotherInstance.getDistanceOf(instance);
// Assert
expect(actual).toBe(expected);
expect(actualSecond).toBe(expected);
expect(throwIfInValidMock).toHaveBeenCalledTimes(expectedCallsForthrowIfInValidMock);
expect(getDistanceUtilityMock).toHaveBeenCalledTimes(expectedCallsForgetDistanceUtilityMock);
// Mock-Restore / Tear down
throwIfInValidMock.mockRestore();
getDistanceUtilityMock.mockRestore();
});
});
});
class DeepCloner {
/**
* Convert given object to string using JSON.stringify
* @param {*} original
*/
static objectToJsonString(original) {
const isInvalidType = typeof (original) === 'function' ||
typeof (original) === 'string' ||
typeof (original) === 'number';
if (!original || isInvalidType) {
throw new Error('Invalid type null/undefined/function/string/number.');
}
return JSON.stringify(original);
}
/**
* Deep clones the given object.
* @param {*} original deep copy the original object.
*/
static deepClone(original) {
return JSON.parse(this.objectToJsonString(original));
}
}
module.exports = { DeepCloner };
const deepCloner = require('../deepCloner').DeepCloner;
const parseMethod = 'parse';
const mockJsonParse = jest.spyOn(JSON, parseMethod);
describe('deep copy/cloning', () => {
describe('DeepCloner.objectToJsonString(object) takes object and converts to string', () => {
// Arrange (Global for this method only)
const errorMessage = 'Invalid type null/undefined/function/string/number.';
const functionInTest = deepCloner.objectToJsonString;
const functionName = functionInTest.name;
test(`[Exist function with 1 parameter] '.${functionName}(arg)' exist and has one argumnet.`, () => {
// Assert
expect(typeof (functionInTest)).toBe('function');
expect(functionInTest.length).toBe(1);
});
// Arrange
const invalidTestCases = [
[null, null],
[undefined, undefined],
[1, 1],
['', 'EmptyString("" or \'\')'],
['validString', 'validString'],
[functionInTest, 'function f() {...}']
];
test.each(invalidTestCases)('[Invalid] Input(%p->%s) should throw Error with message "' + errorMessage + '"', (input) => {
let actualThrownError = null;
// Act
try {
functionInTest(input);
} catch (error) {
actualThrownError = error;
}
// Assert
expect(actualThrownError).not.toBeNull();
expect(actualThrownError.message).toBe(errorMessage);
});
// Arrange
const testCaseIdNull = { id: null };
const testCaseIdStringEmpty = { id: '' };
const testCaseEmptyObjectNotation = {};
const testCaseEmptyArray = [];
const testCaseComplexArray = [testCaseIdNull, testCaseEmptyObjectNotation];
const validTestCases = [
[testCaseIdNull, JSON.stringify(testCaseIdNull)],
[testCaseIdStringEmpty, JSON.stringify(testCaseIdStringEmpty)],
[testCaseEmptyObjectNotation, JSON.stringify(testCaseEmptyObjectNotation)],
[testCaseEmptyArray, JSON.stringify(testCaseEmptyArray)],
[testCaseComplexArray, JSON.stringify(testCaseComplexArray)],
];
test.each(validTestCases)('[Valid] Input(%p) should return "%s"', (input, expected) => {
// Act
const actual = functionInTest(input);
// Assert
expect(actual).toBe(expected);
});
});
describe('DeepCloner.deepClone(object) takes object and converts to JSON and then converts that to object (deep cloning).', () => {
// Arrange (Global for this method only)
const functionInTest = deepCloner.deepClone;
const functionName = functionInTest.name;
test(`[Exist function with 1 parameter] '.${functionName}(arg)' exist and has one argumnet.`, () => {
// Assert
expect(functionName).toBe('deepClone');
expect(typeof (functionInTest)).toBe('function');
expect(functionInTest.length).toBe(1);
});
test(`[Mocked, Mutation] '.${functionName}(arg)' invokes DeepCloner.objectToJsonString(object) at once.`, () => {
// Arrange
const expected = { id: 'called' };
const input = {};
const objectToJsonStringFunctionName = deepCloner.objectToJsonString.name;
const objectToJsonStringMock = jest.spyOn(deepCloner, objectToJsonStringFunctionName);
const expectedCalls = 1;
objectToJsonStringMock.mockImplementation(_ => input);
mockJsonParse.mockImplementation(_ => expected);
deepCloner.objectToJsonString = objectToJsonStringMock;
// Act
const actual = deepCloner.deepClone(input);
// Assert
expect(objectToJsonStringMock).toHaveBeenCalledWith(input);
expect(mockJsonParse).toHaveBeenCalledWith(input);
expect(objectToJsonStringMock).toHaveBeenCalledTimes(expectedCalls);
expect(mockJsonParse).toHaveBeenCalledTimes(expectedCalls);
expect(actual).toBe(expected);
// Mock-Restore / Tear down
mockJsonParse.mockRestore();
objectToJsonStringMock.mockRestore();
});
test(`[Integration] '.${functionName}(arg)' deep clones to nth level, at least 4th level json is cloned properly tested.`, () => {
// Arrange
const originalId = 'org-id-1';
const originalLevel4Value = 'org-level-4';
const propertyToVerify = 'level1.level2.level3.level4.value';
const jsonSample = {
id: originalId,
level1: {
value: 'org-level-1',
level2: {
value: 'org-level-2',
level3: {
value: 'org-level-3',
level4: {
value: originalLevel4Value,
}
}
}
}
};
const clonedId = 'cloned-id-1';
const clonedLevel4Value = 'cloned-level-4';
// Act
const actual = deepCloner.deepClone(jsonSample);
// modify all items in actual
actual.id = clonedId;
actual.level1.level2.level3.level4.value = clonedLevel4Value;
// Assert
// (jsonSample != actual) Just to confrim same memory object is not returned.
// Even with shadow copy it (jsonSample !== actual) will pass.
expect(jsonSample).not.toBe(actual);
expect(jsonSample.id).toBe(originalId);
expect(actual.id).toBe(clonedId);
expect(jsonSample).toHaveProperty(propertyToVerify, originalLevel4Value);
expect(actual).toHaveProperty(propertyToVerify, clonedLevel4Value);
});
});
});
module.exports = {
collectCoverage: true,
coverageDirectory: 'coverage',
verbose: true
}
{
"name": "JestSampleTest",
"version": "1.0.0",
"license": "MIT License",
"keywords": [],
"author": "",
"description": "",
"main": "app.js",
"repository": {
"type": "git",
"url": "git+https://github.com/aukgit/Jest-Sample-Test.git"
},
"bugs": {
"url": "https://github.com/aukgit/Jest-Sample-Test/issues"
},
"homepage": "https://github.com/aukgit/Jest-Sample-Test",
"scripts": {
"test": "jest --coverage",
"dev-all": "jest --coverage --watchAll",
"dev": "jest --coverage --watch"
},
"dependencies": {},
"devDependencies": {
"jest": "25.1.0"
}
}
[
{
"id": 1,
"urlName": "balance-at-work",
"organization": "Balance at Work",
"customerLocations": "across Australia, Pacific and Oceania",
"willWorkRemotely": true,
"website": "http://www.balanceatwork.com.au/",
"services": "At Balance at Work, we want to help you make work a joy for your employees and you! We specialize in leadership development, talent management and career coaching, and use Spidergap as one of our tools to help employees focus their development and achieve more.",
"offices": [
{
"location": "Sydney, Australia",
"address": "Suite 1308, 109 Pitt St \nSydney 2000",
"coordinates": "-33.8934219,151.20404600000006"
}
]
},
{
"id": 2,
"urlName": "spring-development",
"organization": "Spring Development",
"customerLocations": "across the UK",
"willWorkRemotely": true,
"website": "http://www.springdevelopment.net/",
"services": "We provide training, coaching and consultancy to ensure that 360 feedback is delivered positively and constructively, maximising personal development. We can train your people to carry out effective feedback meetings or, if you would benefit from having external, impartial facilitators, we can come and do them for you. We're always happy to have an initial confidential discussion to explore how we can help you.",
"offices": [
{
"location": "Banbury, Oxfordshire",
"address": "Banbury, Oxfordshire",
"coordinates": "52.0629009,-1.3397750000000315"
}
]
},
{
"id": 3,
"urlName": "talent-lab",
"organization": "Talent Lab",
"customerLocations": "across Latin America",
"willWorkRemotely": true,
"website": "http://www.talentlab.mx/",
"services": "We are passionate about people and the capability we all have to evolve and make great things together. We free company's potential through their people through innovative tools, holistic solutions and tailored Human Resources Strategies.",
"offices": [
{
"location": "México City, Mexico",
"address": "Emerson 150 - 503, Colonia Chapultepec Morales, Delegación Miguel Hidalgo, México City, Mexico, CP 11570",
"coordinates": "19.4361004,-99.18870959999998"
}
]
},
{
"id": 4,
"urlName": "blue-square-360",
"organization": "Blue Square 360",
"customerLocations": "globally",
"willWorkRemotely": true,
"website": "http://www.bluesquare360.com/",
"services": "Blue Square 360 provides a professionally managed service covering all areas of a 360° Feedback initiative. We're experienced in supporting projects of all sizes, and always deliver a personal service that provides the level of support you need to ensure your 360 initiative delivers results for the business.",
"offices": [
{
"location": "Singapore",
"address": "Ocean Financial Centre, Level 40, 10 Collyer Quay, Singapore, 049315",
"coordinates": "1.28304,103.85199319999992"
},
{
"location": "London, UK",
"address": "St Saviours Wharf, London SE1 2BE",
"coordinates": "51.5014767,-0.0713608999999451"
}
]
},
{
"id": 5,
"urlName": "raiser",
"organization": "Raiser Resource Group",
"customerLocations": "across Africa",
"willWorkRemotely": true,
"website": "http://www.raiser.co.ke/",
"services": "Raiser Resource Group is a Kenya based company specializing in human performance improvement in Africa. We help individuals and organizations achieve sustainable results that require a change in human behavior and use Spidergap to assess our clients' competency skills.",
"offices": [
{
"location": "Nairobi, Kenya",
"address": "Jumuia Place, Off Lenana Road, Nairobi, Kenya",
"coordinates": "-1.2890117,36.793729600000006"
}
]
},
{
"id": 6,
"urlName": "inspire-ignite",
"organization": "Inspire - Ignite",
"customerLocations": "across the UK",
"willWorkRemotely": true,
"website": "http://www.inspireignite.net/",
"services": "'Inspire - Ignite' is built on the philosophy that people perform better when they can be themselves. We work with individuals and organisations to be their best, achieve ambitions and be a success. We use Clarity4D and the concept of colour and 360 to build personal awareness and action plans.",
"offices": [
{
"location": "Peterborough, UK",
"address": "29 Warren Court, Hampton Hargate, Peterborough, PE7 8HA",
"coordinates": "52.5381398,-0.2713853000000199"
}
]
},
{
"id": 7,
"urlName": "vanessa-gomez",
"organization": "Vanessa Gómez (Freelance consultant)",
"customerLocations": "across Central America",
"willWorkRemotely": true,
"website": null,
"services": "Grow your staff to grow your business! With more than 20 years fostering organizational success for a broad range of companies, I can support you from start to end in uncovering and developing your staff talents, and help them take charge of their own growth.",
"offices": [
{
"location": "San José, Costa Rica",
"address": "Condominio Villas de Valencia 93, San Rafael de Escazú 10203, San José, Costa Rica",
"coordinates": "9.932159,-84.14062790000003"
}
]
},
{
"id": 8,
"urlName": "lead-tcml",
"organization": "LEAD TCM&L",
"customerLocations": "in Europe, UAE and Japan",
"willWorkRemotely": true,
"website": "http://www.leadtcml.com",
"services": "LEAD TCM&L is a Management Consulting and Organizational Development company specialized in evidence-based approaches to Change Management, Human Capital and Leadership Development. We use 360 feedback as an assessment tool in coaching, training, change initiatives and talent management. ",
"offices": [
{
"location": "Rotterdam, Netherlands",
"address": "Rivium Oostlaan 35a, 2909 LL Capelle aan den IJssel, The Netherlands",
"coordinates": "51.9107013,4.547834200000011"
}
]
},
{
"id": 9,
"urlName": "win-with-people",
"organization": "Win With People",
"customerLocations": "in the United States and Canada",
"willWorkRemotely": true,
"website": "http://www.winwithpeople.com/",
"services": "At Win With People, we provide coaching and consulting to bring out the best in people in companies and organizations. We offer behavioral assessments, executive coaching, and 360-degree feedback programs custom-tailored to meet our clients' needs and objectives.",
"offices": [
{
"location": "North Carolina, USA",
"address": "725 Kenmore Road, Chapel Hill, North Carolina USA",
"coordinates": "35.94970259999999,-79.0383625"
}
]
},
{
"id": 10,
"urlName": "leader-development",
"organization": "LeaderDevelopment, Inc.",
"customerLocations": "in the United States and Europe",
"willWorkRemotely": true,
"website": "http://www.leaderdevelopmentinc.com/",
"services": "We want to help you be an exceptional leader - whatever your role or level. We have created a unique, comprehensive and practical Leadership Framework to help you build your organization and career. We use Spidergap and other tools as a critical part of your leadership-development success.",
"offices": [
{
"location": "South Carolina, USA",
"address": "1911 Gadsden Street, Suite 201, Columbia, South Carolina 29201, USA",
"coordinates": "34.0091016,-81.04493960000002"
}
]
},
{
"id": 11,
"urlName": "beacon-search",
"organization": "Beacon Search",
"customerLocations": "in Asia Pacific",
"willWorkRemotely": true,
"website": "http://www.beacon-search.com/",
"services": "At Beacon we specialize in helping companies become and stay profitable by coaching their people assets in becoming better motivated, focused, and effective. We specialize in Executive Search, Executive Coaching and Career Coaching, and use Spidergap as one of our diagnostic tools.",
"offices": [
{
"location": "Singapore",
"address": "31st floor, Suntec Tower 2, 9 Temasek Boulevard, Singapore 038989",
"coordinates": "1.2957136,103.85872429999995"
}
]
},
{
"id": 12,
"urlName": "c2cod",
"organization": "C2C Organizational Development",
"customerLocations": "in Asia, USA and Europe",
"willWorkRemotely": true,
"website": "http://www.c2cod.com/",
"services": "C2C Organizational Development is a high-performing team of OD consultants, facilitators, and content designers who are passionate about seeing people and organizations work better together.",
"offices": [
{
"location": "Bangalore, India",
"address": "Rich Homes Building, 5/1 Richmond Road, Bangalore, India - 560 025",
"coordinates": "12.9657659,77.5998538"
},
{
"location": "Singapore",
"address": "1 Raffles Place, #20-61, Tower 2, Singapore 048616",
"coordinates": "1.284479,103.85108200000002"
}
]
},
{
"id": 13,
"urlName": "gallus-consulting",
"organization": "Gallus Consulting",
"customerLocations": "across the UK",
"willWorkRemotely": true,
"website": "http://www.gallusconsulting.com/",
"services": "We're strategy consultants with a difference - we work with organisations and their leaders to take them from strategy to reality. In our work with leaders we often use 360-degree feedback to identify capability gaps, improve self-awareness, and develop strategic and cultural alignment. Our aim is for believe-able leaders to emerge with the drive, capability and cultural fit to take strategy to reality.",
"offices": [
{
"location": "Northampton",
"address": "Newton House, Northampton Science Park, Moulton Park, Kings Park Road, Northampton, NN3 6LG",
"coordinates": "52.277409,-0.877935999999977"
},
{
"location": "London",
"address": "No1 Royal Exchange, London, EC3V 3DG",
"coordinates": "51.5136102,-0.08757919999993646"
},
{
"location": "Manchester",
"address": "3 Hardman Square, Spinningfields, Manchester, M3 3EB",
"coordinates": "53.47990859999999,-2.2510892999999896"
}
]
},
{
"id": 14,
"urlName": "dwconsulting",
"organization": "Darren Williams Consulting",
"customerLocations": "across Australia",
"willWorkRemotely": true,
"website": "http://www.dwconsulting.com.au/",
"services": "We specialize in Leadership Development and Executive Coaching. Ultimately, leadership development is a process of self development and Spidergap 360 is a great tool for raising awareness of our style and impact on others. We can work with you to design and deliver powerful learning opportunitities.",
"offices": [
{
"location": "Adelaide",
"address": "Adelaide, Australia",
"coordinates": "-34.9284989,138.60074559999998"
}
]
},
{
"id": 15,
"urlName": "lincoln-learning",
"organization": "Lincoln L&D",
"customerLocations": "across Australasia, SE Asia, Africa and the Middle East",
"willWorkRemotely": true,
"website": "http://www.lincolnlearning.com.au/",
"services": "Lincoln Learning & Development facilitates team performance, leadership development and management coaching programs. We take a practical, real-world perspective and look for actionable steps to bring about change.",
"offices": [
{
"location": "Adelaide",
"address": "Unley, SA 5061, Australia",
"coordinates": "-34.95,138.60000000000002"
}
]
},
{
"id": 16,
"urlName": "targetter",
"organization": "Targetter",
"customerLocations": "across Asia, Europe and Africa",
"willWorkRemotely": true,
"website": "https://www.targetter.de/",
"services": "We provide tailor-made leadership development programs and executive coaching engagements. Attendees take full personal responsibility, take action, and foster and grow themselves and their people. Their companies are more competitive, achieve better results and are more profitable.",
"offices": [
{
"location": "Bietigheim-Bissingen, Germany",
"address": "Kleinsachsenheimer Strasse 47, 74321 Bietigheim-Bissingen, Germany",
"coordinates": "48.96277,9.09699999999998"
}
]
},
{
"id": 17,
"urlName": "ask-leadership",
"organization": "Ask Leadership",
"customerLocations": "in the Caribbean, United States, Canada and UK",
"willWorkRemotely": true,
"website": null,
"services": "Ask Leadership is a vibrant business that works to build success for individuals and organizations around the world. Our primary focus is people and team development. The 360 tool is key to seeing where each person stands today and to track their progress as we work with them.",
"offices": [
{
"location": "Trinidad",
"address": "70 Caspian Cove, Westmoorings, Port of Spain, Trinidad",
"coordinates": "10.6792447,-61.56065180000002"
}
]
}
]
const Coordinate = require('./coordinate').Coordinate;
const idealCoordinateString = '51.515419,-0.141099';
const idealCoordinate = new Coordinate(idealCoordinateString);
const fs = require('fs');
class PartnersProfile {
/**
* @param {string} profilePath : Provide an absolute path to the partners file.
*/
constructor(profilePath) {
if (!profilePath || typeof (profilePath) !== 'string') {
throw new Error('Given profile path is empty or invalid.');
}
this.profilePath = profilePath;
}
readFileUsingRequire(path) {
return require(path);
}
/**
* reading json file from file system and returns the JSON of partners as object.
*/
getPartnerProfiles() {
if (!fs.existsSync(this.profilePath)) {
const errorMessage = `File (${this.profilePath}) doesn't exist in the fil system.`;
throw new Error(errorMessage);
}
try {
/*
* Note: For this purpose we are using system operation.
* non sync/async one whould be non-IO blocking.
* However, for this case it is not important.
* To keep things simple non-async one is used.
* */
return this.readFileUsingRequire(this.profilePath);
} catch (error) {
console.error(error);
return null;
}
}
/**
* If the cordinate distance is within 100km then
* returns true or else false.
* @param {PartnerProfile} partnerProfile
*/
filterPartnerProfileWithinHundredKilometers(partnerProfile) {
const isInvalidPartnerProfile = !partnerProfile ||
!partnerProfile.offices ||
!partnerProfile.offices.length;
if (isInvalidPartnerProfile) {
return false;
}
// if any office address within range then returns true for that office
let returningResult = false;
const officeAddressesLength = partnerProfile.offices.length;
for (let officeIndex = officeAddressesLength - 1; officeIndex >= 0; officeIndex--) {
if (this.isCurrentAddressWithinHundredKilomitersRangeExceptDropAddress(partnerProfile, officeIndex)) {
returningResult = true;
}
}
return returningResult;
}
/**
* If address is within 100km range then return true and attaches distance with the office
* If address is not within rage then remove office from the profile given.
* Warning: Mutation occurs on given profile on certain condition.
* @param partnerProfile
* @param officeIndex
*/
isCurrentAddressWithinHundredKilomitersRangeExceptDropAddress(partnerProfile, officeIndex) {
const coordinates = partnerProfile.offices[officeIndex].coordinates;
const currentCordinate = new Coordinate(coordinates);
const distance = idealCoordinate.getDistanceOf(currentCordinate);
const isWithinRange = distance <= 100;
if (!isWithinRange) {
// remove from list.
partnerProfile.offices.splice(officeIndex, 1);
return false;
}
// Attaching the distance for future access or debugging or testing.
partnerProfile.offices[officeIndex].distance = distance;
return true;
}
getQuickestDistanseProfilesWithinHundredKilometers() {
const profiles = this.getPartnerProfiles();
// Note: Not using profiles.filter, because
// this.filterPartnerProfileWithinHundredKilometers calls other functions from inside.
if (!profiles || !profiles.length) {
console.log('No partners profile found');
return;
}
const results = [];
profiles.forEach(profile => {
if (this.filterPartnerProfileWithinHundredKilometers(profile)) {
results.push(profile);
}
});
return results;
}
partnerProfileOrderByCompanyNameAscending(firstProfile, secondProfile) {
const isInvalid = !firstProfile ||
!secondProfile ||
!firstProfile.organization ||
!secondProfile.organization;
if (isInvalid) {
const message = 'Given profile is either invalid or doesn\' have "organization" property.';
console.error(message);
throw new Error(message);
}
return firstProfile.organization - secondProfile.organization;
}
getPartnerProfilesWithinHundredKilometersOrderByCompanyNamesDisplay() {
const profilesWithinFilter = this.getQuickestDistanseProfilesWithinHundredKilometers();
if (!profilesWithinFilter || !profilesWithinFilter.length) {
console.warn('No filter data found within 100km rage.');
return;
}
const orderByProfiles = profilesWithinFilter.sort(this.partnerProfileOrderByCompanyNameAscending);
const results = [];
orderByProfiles.forEach(profile => {
(profile.offices || []).forEach(office => {
const distance = office.distance;
const distanceDisplay = ',\n ---- (lat, long) = (' + office.coordinates + '), Distance: ' + distance;
const display = ' Company/Organization Name: "' + profile.organization + '",\n --- Address: "' + office.address + '"' + distanceDisplay;
results.push(display);
});
});
return results;
}
debugPrintPartnerProfilesWithinHundredKilometersOrderByCompanyName() {
const profilesDisplay = this.getPartnerProfilesWithinHundredKilometersOrderByCompanyNamesDisplay();
if (!profilesDisplay || !profilesDisplay.length) {
console.log('No records found within range.');
return;
}
profilesDisplay.forEach(profileDisplay => {
console.log(profileDisplay);
});
}
}
module.exports = { PartnersProfile, IdealCoordinate: idealCoordinate };
const Coordinate = require('../coordinate').Coordinate;
const idealCoordinate = require('../partnersProfile').IdealCoordinate;
const PartnersProfile = require('../partnersProfile').PartnersProfile;
const path = ',notValidPath';
const instance = new PartnersProfile(path);
const deepCloner = require('../../cloner/deepCloner').DeepCloner;
const fs = require('fs');
const sampleProfiles = [
{
"id": 4,
"urlName": "blue-square-360",
"organization": "Blue Square 360",
"customerLocations": "globally",
"willWorkRemotely": true,
"website": "http://www.bluesquare360.com/",
"services": "Blue Square 360 provides a professionally managed service covering all areas of a 360° Feedback initiative. We're experienced in supporting projects of all sizes, and always deliver a personal service that provides the level of support you need to ensure your 360 initiative delivers results for the business.",
"offices": [
{
"location": "Singapore",
"address": "Ocean Financial Centre, Level 40, 10 Collyer Quay, Singapore, 049315",
"coordinates": "1.28304,103.85199319999992"
},
{
"location": "London, UK",
"address": "St Saviours Wharf, London SE1 2BE",
"coordinates": "51.5014767,-0.0713608999999451"
}
]
},
{
"id": 13,
"urlName": "gallus-consulting",
"organization": "Gallus Consulting",
"customerLocations": "across the UK",
"willWorkRemotely": true,
"website": "http://www.gallusconsulting.com/",
"services": "We're strategy consultants with a difference - we work with organisations and their leaders to take them from strategy to reality. In our work with leaders we often use 360-degree feedback to identify capability gaps, improve self-awareness, and develop strategic and cultural alignment. Our aim is for believe-able leaders to emerge with the drive, capability and cultural fit to take strategy to reality.",
"offices": [
{
"location": "Northampton",
"address": "Newton House, Northampton Science Park, Moulton Park, Kings Park Road, Northampton, NN3 6LG",
"coordinates": "52.277409,-0.877935999999977"
},
{
"location": "London",
"address": "No1 Royal Exchange, London, EC3V 3DG",
"coordinates": "51.5136102,-0.08757919999993646"
},
{
"location": "Manchester",
"address": "3 Hardman Square, Spinningfields, Manchester, M3 3EB",
"coordinates": "53.47990859999999,-2.2510892999999896"
}
]
},
];
describe('PartnersProfile class tests', () => {
describe('[Constructor Testing] .constructor(string)', () => {
// Arrange (Global for this method only)
const errorMessage = 'Given profile path is empty or invalid.';
const invalidTestCases = [
[null, null],
[undefined, undefined],
[1, 'AnyNumber'],
[{}, '{}(AnyObject)'],
];
test.each(invalidTestCases)('[Invlaid] Input(%p -> %s) -> PartnersProfile.constructor(string) throws error.', (input) => {
// Arrange
let actualThrownError = null;
let actual = null;
// Act
try {
actual = new PartnersProfile(input);
} catch (error) {
actualThrownError = error;
}
// Assert
expect(actualThrownError).not.toBeNull();
expect(actualThrownError.message).toBe(errorMessage);
});
test(`[Invlaid] String('anystring,$$##@') PartnersProfile.constructor(string -> meaning giving invalid path string) -> doesn't throw any error.`, () => {
// Arrange
const input = 'anystring,$$##@';
// Act
const actual = new PartnersProfile(input);
// Assert
expect(actual).not.toBeNull();
expect(actual).toBeDefined();
expect(actual.profilePath).toBe(input);
});
});
describe('.debugPrintPartnerProfilesWithinHundredKilometersOrderByCompanyName() displays ', () => {
const expectedMessage = 'No records found within range.';
const testCases = [
[null, expectedMessage],
[undefined, expectedMessage],
[[], expectedMessage],
[['Hello World1'], 'Hello World1']
]
test.each(testCases)(`When (%p), prints "%s"`, (returnArray, message) => {
// Arrange
const mockingMethodName = instance.getPartnerProfilesWithinHundredKilometersOrderByCompanyNamesDisplay.name;
const getPartnerProfilesWithinHundredKilometersOrderByCompanyNamesDisplayMock = jest.spyOn(instance, mockingMethodName);
getPartnerProfilesWithinHundredKilometersOrderByCompanyNamesDisplayMock.mockImplementation(_ => returnArray);
const consoleLogMock = jest.spyOn(global.console, 'log');
consoleLogMock.mockImplementation(_ => { });
const expectedCalls = 1;
const expectedConsoleCalls = 1;
// Act
instance.debugPrintPartnerProfilesWithinHundredKilometersOrderByCompanyName();
// Assert
expect(consoleLogMock).toHaveBeenCalledTimes(expectedConsoleCalls);
expect(consoleLogMock).toHaveBeenCalledWith(message);
expect(getPartnerProfilesWithinHundredKilometersOrderByCompanyNamesDisplayMock).toHaveBeenCalledTimes(expectedCalls);
// Mock-Restore / Tear down
consoleLogMock.mockRestore();
getPartnerProfilesWithinHundredKilometersOrderByCompanyNamesDisplayMock.mockRestore();
});
});
describe('.partnerProfileOrderByCompanyNameAscending() ascending sorting (f.organization - l.organization) ', () => {
const expectedMessage = 'Given profile is either invalid or doesn\' have "organization" property.';
const validProfileWithOrganization = { organization: 5 };
const validProfileWithOrganization2 = { organization: 3 };
const inValidProfileWithOrganization = {};
const invalidTestCases = [
[[null, null], expectedMessage],
[[undefined, undefined], expectedMessage],
[[validProfileWithOrganization, undefined], expectedMessage],
[[inValidProfileWithOrganization, validProfileWithOrganization], expectedMessage],
]
test.each(invalidTestCases)(`[Invalid] When (%p), prints "%s"`, (inputArray, message) => {
// Arrange
const consoleMock = jest.spyOn(global.console, 'error');
consoleMock.mockImplementation(_ => { });
const expectedConsoleCalls = 1;
let actualError = null;
// Act
try {
instance.partnerProfileOrderByCompanyNameAscending(inputArray[0], inputArray[1]);
} catch (error) {
actualError = error;
}
// Assert
expect(actualError).not.toBeNull();
expect(actualError.message).toBe(message);
expect(consoleMock).toHaveBeenCalledTimes(expectedConsoleCalls);
expect(consoleMock).toHaveBeenCalledWith(message);
// Mock-Restore / Tear down
consoleMock.mockRestore();
});
test(`[Integration, Valid] When (organization:5 - organization:3) returns 2.`, () => {
// Arrange
const expected = 2;
// Act
const actual = instance.partnerProfileOrderByCompanyNameAscending(validProfileWithOrganization, validProfileWithOrganization2);
// Assert
expect(actual).toBe(expected);
});
});
describe('.getPartnerProfilesWithinHundredKilometersOrderByCompanyNamesDisplay() gets filtered profiles display array.', () => {
test(`[Mutation] getQuickestDistanseProfilesWithinHundredKilometers() must be called at once.`, () => {
// Arrange
const expectedCalls = 1;
const message = 'No filter data found within 100km rage.';
const mock = jest.spyOn(instance, instance.getQuickestDistanseProfilesWithinHundredKilometers.name);
mock.mockImplementation(_ => []);
const consoleMock = jest.spyOn(global.console, 'warn');
consoleMock.mockImplementation(_ => { });
// Act
instance.getPartnerProfilesWithinHundredKilometersOrderByCompanyNamesDisplay();
// Assert
expect(mock).toHaveBeenCalledTimes(expectedCalls);
expect(consoleMock).toHaveBeenCalledWith(message);
// Mock-Restore / Tear down
mock.mockRestore();
consoleMock.mockRestore();
});
test(`[Integration] creates display string.`, () => {
// Arrange
const expectedCalls = 1;
const mock = jest.spyOn(instance, instance.getQuickestDistanseProfilesWithinHundredKilometers.name);
const profiles = deepCloner.deepClone(sampleProfiles);
profiles[0].offices = undefined;
mock.mockImplementation(_ => profiles);
// Act
const actualResults = instance.getPartnerProfilesWithinHundredKilometersOrderByCompanyNamesDisplay();
// Assert
expect(mock).toHaveBeenCalledTimes(expectedCalls);
expect(actualResults).toBeDefined();
for (let index = 0; index < actualResults.length; index++) {
const profileDisplay = actualResults[index];
expect(profileDisplay.indexOf(' Company/Organization Name: "') > -1).toBeTruthy();
}
// Mock-Restore / Tear down
mock.mockRestore();
});
});
describe('.getQuickestDistanseProfilesWithinHundredKilometers() gets filtered profiles.', () => {
test(`[Mutation] getPartnerProfiles() must be called at once.`, () => {
// Arrange
const expectedCalls = 1;
const message = 'No partners profile found';
const mock = jest.spyOn(instance, instance.getPartnerProfiles.name);
mock.mockImplementation(_ => []);
const consoleMock = jest.spyOn(global.console, 'log');
consoleMock.mockImplementation(_ => { });
// Act
instance.getQuickestDistanseProfilesWithinHundredKilometers();
// Assert
expect(mock).toHaveBeenCalledTimes(expectedCalls);
expect(consoleMock).toHaveBeenCalledWith(message);
// Mock-Restore / Tear down
mock.mockRestore();
consoleMock.mockRestore();
});
test(`[Mutation] filterPartnerProfileWithinHundredKilometers() must be called at once for filtering.`, () => {
// Arrange
const expectedCalls = 1;
const mock = jest.spyOn(instance, instance.getPartnerProfiles.name);
mock.mockImplementation(_ => sampleProfiles);
const filterMock = jest.spyOn(instance, instance.filterPartnerProfileWithinHundredKilometers.name);
filterMock.mockImplementation(_ => true);
// Act
const actualResults = instance.getQuickestDistanseProfilesWithinHundredKilometers();
// Assert
expect(actualResults).toBeDefined();
expect(actualResults).toStrictEqual(sampleProfiles);
expect(mock).toHaveBeenCalledTimes(expectedCalls);
expect(filterMock).toHaveBeenCalledTimes(sampleProfiles.length);
// Mock-Restore / Tear down
mock.mockRestore();
filterMock.mockRestore();
});
});
describe('.isCurrentAddressWithinHundredKilomitersRangeExceptDropAddress(partnerProfile, officeIndex) returns true if within 100Km range and mutates or removes office address from array if not within range.', () => {
test(`[Within Range] Is within 100km range, adds distance and don't remove from office address.`, () => {
// Arrange
const expectedCalls = 1;
const getDistanceMock = jest.spyOn(idealCoordinate, 'getDistanceOf');
const returningDistance = 96;
getDistanceMock.mockImplementation(_ => returningDistance);
const sampleProfile = deepCloner.deepClone(sampleProfiles[0]);
const officeIndex = 0;
const coordinates = sampleProfile.offices[officeIndex].coordinates;
const currentCordinate = new Coordinate(coordinates);
// Act
const actual = instance.isCurrentAddressWithinHundredKilomitersRangeExceptDropAddress(sampleProfile, officeIndex);
// Assert
expect(actual).toBeTruthy();
expect(getDistanceMock).toHaveBeenCalledTimes(expectedCalls);
expect(getDistanceMock).toHaveBeenCalledWith(currentCordinate);
expect(sampleProfile.offices[officeIndex].distance).toBe(returningDistance);
// Mock-Restore / Tear down
getDistanceMock.mockRestore();
});
test(`[NOT Within Range] Is NOT within 100km range (> 100KM), removes address from the offices array.`, () => {
// Arrange
const expectedCalls = 1;
const getDistanceMock = jest.spyOn(idealCoordinate, 'getDistanceOf');
const returningDistance = 110;
getDistanceMock.mockImplementation(_ => returningDistance);
const sampleProfile = deepCloner.deepClone(sampleProfiles[0]);
const officeIndex = 0;
const coordinates = sampleProfile.offices[officeIndex].coordinates;
const officesLength = sampleProfile.offices.length;
const currentOffice = sampleProfile.offices[officeIndex];
const currentCordinate = new Coordinate(coordinates);
// Act
const actual = instance.isCurrentAddressWithinHundredKilomitersRangeExceptDropAddress(sampleProfile, officeIndex);
// Assert
expect(actual).toBeFalsy();
expect(getDistanceMock).toHaveBeenCalledTimes(expectedCalls);
expect(getDistanceMock).toHaveBeenCalledWith(currentCordinate);
expect(sampleProfile.offices.length).toBe(officesLength - 1);
expect(sampleProfile.offices[officeIndex]).not.toBe(currentOffice);
expect(sampleProfile.offices[officeIndex].address).not.toBe(currentOffice.address);
// Mock-Restore / Tear down
getDistanceMock.mockRestore();
});
});
describe('.getPartnerProfiles() reads profile json data file from file system.', () => {
test(`[Using Mock] Throws error if given path is not exist in the system.`, () => {
// Arrange
const expectedCalls = 1;
const expectedErrorMessage = `File (${instance.profilePath}) doesn't exist in the fil system.`
const getExistsSyncMock = jest.spyOn(fs, fs.existsSync.name);
getExistsSyncMock.mockImplementation(_ => false);
let actualError;
// Act
try {
instance.getPartnerProfiles();
} catch (error) {
actualError = error;
}
// Assert
expect(actualError).toBeDefined();
expect(actualError.message).toBe(expectedErrorMessage);
expect(getExistsSyncMock).toHaveBeenCalledTimes(expectedCalls);
// Mock-Restore / Tear down
getExistsSyncMock.mockRestore();
});
test(`[Integration] Throws error if given path is not exist in the system.`, () => {
// Arrange
const expectedErrorMessage = `File (${instance.profilePath}) doesn't exist in the fil system.`
let actualError;
// Act
try {
instance.getPartnerProfiles();
} catch (error) {
actualError = error;
}
// Assert
expect(actualError).toBeDefined();
expect(actualError.message).toBe(expectedErrorMessage);
});
test(`[Valid Path, Using Mock] Returns JSON data.`, () => {
// Arrange
const expectedCalls = 1;
const readFileMock = jest.spyOn(instance, instance.readFileUsingRequire.name);
const expected = sampleProfiles;
readFileMock.mockImplementation(_ => expected);
const getExistsSyncMock = jest.spyOn(fs, fs.existsSync.name);
getExistsSyncMock.mockImplementation(_ => true);
// Act
const actual = instance.getPartnerProfiles();
// Assert
expect(actual).not.toBeNull();
expect(actual).not.toBeUndefined();
expect(readFileMock).toHaveBeenCalledTimes(expectedCalls);
expect(readFileMock).toHaveBeenCalledWith(instance.profilePath);
expect(actual).toBe(expected);
// Mock-Restore / Tear down
readFileMock.mockRestore();
getExistsSyncMock.mockRestore();
});
test(`[Valid Path, Using Mock] Returns null and console log while have error during read.`, () => {
// Arrange
const expectedCalls = 1;
const readFileMock = jest.spyOn(instance, instance.readFileUsingRequire.name);
const mockedError = new Error('Mocked Error');
readFileMock.mockImplementation(_ => {
throw mockedError;
});
const getExistsSyncMock = jest.spyOn(fs, fs.existsSync.name);
getExistsSyncMock.mockImplementation(_ => true);
const consoleMock = jest.spyOn(global.console, 'error');
consoleMock.mockImplementation(_ => { });
// Act
const actual = instance.getPartnerProfiles();
// Assert
expect(actual).toBeNull();
expect(readFileMock).toHaveBeenCalledTimes(expectedCalls);
expect(readFileMock).toHaveBeenCalledWith(instance.profilePath);
expect(consoleMock).toHaveBeenCalledTimes(expectedCalls);
expect(consoleMock).toHaveBeenCalledWith(mockedError);
// Mock-Restore / Tear down
readFileMock.mockRestore();
getExistsSyncMock.mockRestore();
consoleMock.mockRestore();
});
});
describe(`.filterPartnerProfileWithinHundredKilometers() returns true if any address is within range of 100km from IdealCoordinate(${idealCoordinate.coordinates}).`, () => {
test(`[Invalid, Integration] Returns false 'office' property doesn't exist or 'office'(undefined/null/[]) or profile is undefined/null.`, () => {
// Arrange
const sampleProfile = deepCloner.deepClone(sampleProfiles[0]);
sampleProfile.offices = [];
// Act
const actual = instance.filterPartnerProfileWithinHundredKilometers(sampleProfile);
const actual2 = instance.filterPartnerProfileWithinHundredKilometers(null);
// Assert
expect(actual).toBeFalsy();
expect(actual2).toBeFalsy();
});
const validSampleProfile = deepCloner.deepClone(sampleProfiles[0]);
test(`[Valid, Using Mock] If office address coordinate is not within range then returns false.`, () => {
// Arrange
const expectedCalls = 2; // since two office addresses
const isAddressExistMock = jest.spyOn(instance, instance.isCurrentAddressWithinHundredKilomitersRangeExceptDropAddress.name);
isAddressExistMock.mockImplementation(_ => false);
// Act
const actual = instance.filterPartnerProfileWithinHundredKilometers(validSampleProfile);
// Assert
expect(actual).toBeFalsy();
expect(isAddressExistMock).toHaveBeenCalledTimes(expectedCalls);
// Mock-Restore / Tear down
isAddressExistMock.mockRestore();
});
test(`[Valid Input, Using Mock] If office (any) address coordinate is not within range then returns 'true'.`, () => {
// Arrange
const expectedCalls = 2; // since two office addresses
const isAddressExistMock = jest.spyOn(instance, instance.isCurrentAddressWithinHundredKilomitersRangeExceptDropAddress.name);
isAddressExistMock.mockImplementation(_ => true);
// Act
const actual = instance.filterPartnerProfileWithinHundredKilometers(validSampleProfile);
// Assert
expect(actual).toBeTruthy();
expect(isAddressExistMock).toHaveBeenCalledTimes(expectedCalls);
// Mock-Restore / Tear down
isAddressExistMock.mockRestore();
});
});
it('[Integration] idealCoordinate should be "51.515419,-0.141099"', () => {
expect(idealCoordinate.coordinates).toBe('51.515419,-0.141099');
});
it('[Integration] .readFileUsingRequire() throws exception if invalid path given."', () => {
// Arrange
let actualError, actualErrorMessage;
const invalidPath = 'invalid';
const errorMessageContains = `Cannot find module 'invalid' from`;
// Act
try {
instance.readFileUsingRequire(invalidPath);
} catch (error) {
actualError = error;
actualErrorMessage = error.message;
}
// Assert
expect(actualError).toBeDefined();
expect(actualErrorMessage).toBeDefined();
expect(actualErrorMessage).toContain(errorMessageContains);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment