jasmine spy not finding property - javascript

I have a file that basically looks like this(shortened)
const octokit = new (require("#octokit/rest"))();
function buildRepo(name) {
fs.promises
.readFile("data/settings.json")
.then(data => JSON.parse(data))
.then(settings => settings.repositories.find(repo => repo.name === name))
.then(repo => {
let repoName = repo.url
.substring(repo.url.lastIndexOf("/") + 1)
.slice(0, -4);
let jobName = repo.name;
return octokit.repos
.get({
owner: "munhunger",
repo: repoName
})
.then(({ data }) => {
...
});
});
}
module.exports = { buildRepo };
And so I want to write a test for what it does with the data that it gets from the octokit.repos.get function. But since that function will go out to the internet and look at GitHub repositories, I want to mock it.
I have a few tests running with jasmine, and I read up slightly on it and it seems as if jasmine should be able to mock this for me.
However, the test that I have written seems to fail.
const builder = require("./index");
describe("spyOn", () => {
it("spies", () => {
spyOnProperty(builder, "octokit");
builder.buildRepo("blitzbauen");
});
});
With the error octokit property does not exist. What am I doing wrong here? would I need to add octokit to module.exports?(which seems rather insane)

Yes, you'd need to add Octokit to module.exports since you now only export buildRepo.
Anything from a module that is not exported can't be accessed directly by other modules, so if it should be accessible it should be exported.
Alternatively, you may be able to mock the entire Octokit module with Jasmine so any calls by any script are made to the mocked version, but I'm not sure how you'd go about doing that since my experience with Jasmine is limited

Related

What is considered the correct way to test methods that return http observables?

I have a TypeScript project which I would like to deploy as JS NPM package. This package performs some http requests using rxjs ajax functions. Now I would like to write tests for these methods.
At some point I have a method like this (simplified!):
getAllUsers(): Observable<AjaxResponse> {
return ajax.get(this.apiUrl + '/users');
}
I know about basic testing, for example with spyOn I can mock a response from the server. But how would I actually test the http request?
The documentation of jasmine says that I cannot do async work in the it part, but in the beforeEach: https://jasmine.github.io/tutorials/async
Would this be the correct approach to test the API?
let value: AjaxResponse;
let error: AjaxError;
beforeEach((done) => {
const user = new UsersApi();
user.getAllUsers().subscribe(
(_value: any) => {
value = _value;
done();
},
(_error: any) => {
error = _error;
done();
}
);
});
it("should test the actual http request", () => {
// Test here something
// expect(value).toBe...
// expect(error).toBe...
});
I couldn't think of another approach how to do the async work...
You need to mock ajax.get to return an Observable that emits values that you want to test.
This is done depending on how ajax is declared in your file that contains user.getAllUsers method.
It'd be ideal if UsersApi() had ajax passed into it (pure function style) because then you could just do something like this:
e.g.
class UsersApi {
public ajax;
constructor(ajax) {
this.ajax = ajax;
}
getAllUsers() {
return this.ajax.get(....)
}
}
Edit: Passing in dependencies (aka dependency injection) is one thing that makes modules like this significantly easier to test - consider doing it!
Then you could very easily mock your tests out like this:
const someSuccessfulResponse = ...
const someFailedResponse = ...
const ajaxWithSuccess = {
get:jest.fn(() => of(someSuccessfulResponse))
}
const ajaxWithFailed = {
get:jest.fn(() => throwError(someFailedResponse))
}
describe('my test suite',() => {
it("should test a successful response", (done) => {
const user = new UsersApi(ajaxWithSuccess);
user.getAllUsers().subscribe(d => {
expect(d).toBe(someSuccessfulResponse);
done();
});
});
it("should test a failed response", (done) => {
const user = new UsersApi(ajaxWithFailed);
user.getAllUsers().subscribe(null,error => {
expect(d).toBe(someFailedResponse);
done();
});
});
});
Note: You DO NOT want to test the actual API request. You want to test that your code successfully handles whatever API responses you think it could receive. Think about it, how are you going to test if a failed API response is handled correctly by your code if your API always returns 200s?
EDIT #27: The above code works fine for me when I run jest, not totally clear on why jasmine (doesn't jest run on jasmine?) says it can't do async in it's. In any case, you could just change the code above to set everything up in the beforeEach and just do your expects in the it's.

What is the state of the art for testing/mocking functions within a module in 2018?

I have a module, for the purposes of learning testing, that looks like this:
api.js
import axios from "axios";
const BASE_URL = "https://jsonplaceholder.typicode.com/";
const URI_USERS = 'users/';
export async function makeApiCall(uri) {
try {
const response = await axios(BASE_URL + uri);
return response.data;
} catch (err) {
throw err.message;
}
}
export async function fetchUsers() {
return makeApiCall(URI_USERS);
}
export async function fetchUser(id) {
return makeApiCall(URI_USERS + id);
}
export async function fetchUserStrings(...ids) {
const users = await Promise.all(ids.map(id => fetchUser(id)));
return users.map(user => parseUser(user));
}
export function parseUser(user) {
return `${user.name}:${user.username}`;
}
Pretty straight forward stuff.
Now I want to test that fetchUserStrings method, and to do that I want to mock/spy on both fetchUser and parseUser. At the same time - I don't want the behaviour of parseUser to stay mocked - for when I'm actually testing that.
I run in the problem that it seems that it is not possible to mock/spy on functions within the same module.
Here are the resources I've read about it:
How to mock a specific module function? Jest github issue. (100+ thumbs up).
where we're told:
Supporting the above by mocking a function after requiring a module is impossible in JavaScript – there is (almost) no way to retrieve the binding that foo refers to and modify it.
The way that jest-mock works is that it runs the module code in isolation and then retrieves the metadata of a module and creates mock functions. Again, in this case it won't have any way to modify the local binding of foo.
Refer to the functions via an object
The solution he proposes is ES5 - but the modern equivalent is described in this blog post:
https://luetkemj.github.io/170421/mocking-modules-in-jest/
Where, instead of calling my functions directly, I refer to them via an object like:
api.js
async function makeApiCall(uri) {
try {
const response = await axios(BASE_URL + uri);
return response.data;
} catch (err) {
throw err.message;
}
}
async function fetchUsers() {
return lib.makeApiCall(URI_USERS);
}
async function fetchUser(id) {
return lib.makeApiCall(URI_USERS + id);
}
async function fetchUserStrings(...ids) {
const users = await Promise.all(ids.map(id => lib.fetchUser(id)));
return users.map(user => lib.parseUser(user));
}
function parseUser(user) {
return `${user.name}:${user.username}`;
}
const lib = {
makeApiCall,
fetchUsers,
fetchUser,
fetchUserStrings,
parseUser
};
export default lib;
Other posts that suggest this solution:
https://groups.google.com/forum/#!topic/sinonjs/bPZYl6jjMdg
https://stackoverflow.com/a/45288360/1068446
And this one seems to be a variant of the same idea:
https://stackoverflow.com/a/47976589/1068446
Break the object into modules
An alternative, is that I would break my module up, such that I'm never calling functions directly within each other.
eg.
api.js
import axios from "axios";
const BASE_URL = "https://jsonplaceholder.typicode.com/";
export async function makeApiCall(uri) {
try {
const response = await axios(BASE_URL + uri);
return response.data;
} catch (err) {
throw err.message;
}
}
user-api.js
import {makeApiCall} from "./api";
export async function fetchUsers() {
return makeApiCall(URI_USERS);
}
export async function fetchUser(id) {
return makeApiCall(URI_USERS + id);
}
user-service.js
import {fetchUser} from "./user-api.js";
import {parseUser} from "./user-parser.js";
export async function fetchUserStrings(...ids) {
const users = await Promise.all(ids.map(id => lib.fetchUser(id)));
return ids.map(user => lib.parseUser(user));
}
user-parser.js
export function parseUser(user) {
return `${user.name}:${user.username}`;
}
And that way I can mock the dependency modules when I'm testing the dependant module, no worries.
But I'm not sure that breaking up the modules like this is even feasible - I imagine that there might be a circumstance where you have circular dependencies.
There are some alternatives:
Dependency injection in the function:
https://stackoverflow.com/a/47804180/1068446
This one looks ugly as though, imo.
Use babel-rewire plugin
https://stackoverflow.com/a/52725067/1068446
I have to admit - I haven't looked at this much.
Split your test into multiple files
Am investigating this one now.
My question: This is all quite a frustrating and fiddly way of testing - is there a standard, nice and easy, way people are writing unit tests in 2018, that specifically solve this issue?
As you've already discovered attempting to directly test an ES6 module is extremely painful. In your situation it sounds like you are transpiling the ES6 module rather than testing it directly, which would likely generate code that looks something like this:
async function makeApiCall(uri) {
...
}
module.exports.makeApiCall = makeApiCall;
Since the other methods are calling makeApiCall directly, rather than the export, even if you tried to mock the export nothing would happen. As it stands ES6 module exports are immutable, so even if you did not transpile the module you would likely still have issues.
Attaching everything to a "lib" object is probably the easiest way to get going, but it feels like a hack, not a solution. Alternatively using a library that can rewire the module is a potential solution, but its extremely hokey and in my opinion it smells. Usually when you're running into this type of code smell you have a design problem.
Splitting up the modules into tiny pieces feels like a poor mans dependency injection, and as you've stated you'll likely run into issues quickly. Real dependency injection is probably the most robust solution, but it's something you need to build from the ground up, it's not something that you can just plug into an existing project and expect to have things working immediately.
My suggestion? Create classes and use them for testing instead, then just make the module a thin wrapper over an instance of the class. Since you're using a class you'll always be referencing your method calls using a centralized object (the this object) which will allow you to mock out the things you need. Using a class will also give you an opportunity to inject data when you construct the class, giving you extremely fine grained control in your tests.
Let's refactor your api module to use a class:
import axios from 'axios';
export class ApiClient {
constructor({baseUrl, client}) {
this.baseUrl = baseUrl;
this.client = client;
}
async makeApiCall(uri) {
try {
const response = await this.client(`${this.baseUrl}${uri}`);
return response.data;
} catch (err) {
throw err.message;
}
}
async fetchUsers() {
return this.makeApiCall('/users');
}
async fetchUser(id) {
return this.makeApiCall(`/users/${id}`);
}
async fetchUserStrings(...ids) {
const users = await Promise.all(ids.map(id => this.fetchUser(id)));
return users.map(user => this.parseUser(user));
}
parseUser(user) {
return `${user.name}:${user.username}`;
}
}
export default new ApiClient({
url: "https://jsonplaceholder.typicode.com/",
client: axios
});
Now lets create some tests for the ApiClient class:
import {ApiClient} from './api';
describe('api tests', () => {
let api;
beforeEach(() => {
api = new ApiClient({
baseUrl: 'http://test.com',
client: jest.fn()
});
});
it('makeApiCall should use client', async () => {
const response = {data: []};
api.client.mockResolvedValue(response);
const value = await api.makeApiCall('/foo');
expect(api.client).toHaveBeenCalledWith('http://test.com/foo');
expect(value).toBe(response.data);
});
it('fetchUsers should call makeApiCall', async () => {
const value = [];
jest.spyOn(api, 'makeApiCall').mockResolvedValue(value);
const users = await api.fetchUsers();
expect(api.makeApiCall).toHaveBeenCalledWith('/users');
expect(users).toBe(value);
});
});
I should note that I have not tested if the provided code works, but hopefully the concept is clear enough.

Jest - mock a property and function from moment-timezone

Im trying to mock property tz and a function using jest but i dont know to mock both things together:
If run something like:
jest.mock('moment-timezone', () => () => ({weekday: () => 5}))
jest.mock('moment-timezone', () => {
return {
tz: {
}
}
})
I can mock attribute tz or instruction moment(). How can i write a mock for cover this code?
const moment = require('moment-timezone')
module.exports.send = () => {
const now = moment()
moment.tz.setDefault('America/Sao_Paulo')
return now.weekday()
}
Thanks
You could take advantage of the second parameter of jest.mock(), which would let you specify a custom implementation of the mocked module to use in testing.
Inside this custom implementation, you can also define some convenience helpers to emulate expected implementation values (e.g. weekday()).
// send-module.test.js
jest.mock('moment-timezone', () => {
let weekday
const moment = jest.fn(() => {
return {
weekday: jest.fn(() => weekday),
}
})
moment.tz = {
setDefault: jest.fn(),
}
// Helper for tests to set expected weekday value
moment.__setWeekday = (value) => weekday = value
return moment;
})
const sendModule = require('./send-module')
test('test', () => {
require('moment-timezone').__setWeekday(3)
expect(sendModule.send()).toBe(3)
})
Do note that manually providing the mock per test file can get tedious and repetitive if the module being mocked has a huge API surface. To address the latter case, you can consider authoring some manual mocks to make them reusable (i.e. using the __mocks__ directory convention) and supplement that by using jest.genMockFromModule().
The Jest documentation has some guidance about this.

Sinon Stub not working for exported function

Sinon doesn't seem to be stubbing a method from an imported file. Is it to do with exporting consts?
I see "Received ORIGINAL MESSAGE" in the console.log.
Main.js
import * as otherActions from 'filters/actions/Other.actions';
describe('filter actions', () => {
it('should log STUBBED MESSAGE', () => {
sinon.stub(otherActions, 'logMessage').callsFake(m => console.log('STUBBED Message'));
const compiled = otherActions.doSomethingAndLogMessage(5, 5);
compiled(message => console.log(`RECEIVED ${message}`), () => {});
});
});
Other.actions.js
export const logMessage = () => console.log("ORIGINAL MESSAGE");
export const doSomethingAndLogMessage = (categoryId, size) => (dispatch, getState) => {
dispatch(logMessage());
};
The problem is occurring because you are stubbing the function in the exported module, when referenced in the module context. You are not stubbing when referencing it raw from inside the module. There are many ways to fix this, but I think all will require you to change your production code a bit.
One suggestion is this:
Other.actions.js
export const logger = { message: () => console.log("ORIGINAL MESSAGE") };
Main.js
import * as otherActions from 'filters/actions/Other.actions';
...
sinon.stub(otherActions.logger, 'message')
.callsFake(m => console.log('STUBBED Message'));
The important thing is that you create the stub in a context that is available to the module under test.
And another general comment is that typically, you don't want to mock or stub functions or methods in the module you are testing. Typically, the unit of unit testing refers to a module. And so, if you find the need to stub out something in the same module you are testing, then I'd suggest that your module boundaries are not correct.

How to mock dynamical for mongoDB method?

In my application code there are several places, where I have to connect to a DB and get some data.
For my unit tests (I'm using JestJS), I need to mock this out.
Let's assume this simple async function:
/getData.js
import DB from './lib/db'
export async function getData () {
const db = DB.getDB()
const Content = db.get('content')
const doc = await Content.findOne({ _id: id })
return doc
}
The DB connection is in a separate file:
/lib/db.js
import monk from 'monk'
var state = {
db: null
}
exports.connect = (options, done) => {
if (state.db) return done()
state.db = monk(
'mongodb://localhost:27017/db',
options
)
return state.db
}
exports.getDB = () => {
return state.db
}
You can see, I'll recieve the DB and get a collection. After this I will recieve the data.
My attempt for the mock so far:
/tests/getData.test.js
import { getData } from '../getData'
import DB from './lib/db'
describe('getData()', () => {
beforeEach(() => {
DB.getDB = jest.fn()
.mockImplementation(
() => ({
get: jest.fn(
() => ({
findOne: jest.fn(() => null)
})
)
})
)
})
test('should return null', () => {
const result = getData()
expect(result).toBeNull()
})
})
Maybe this is not the best way to do it...? I'm very happy for every improvement.
My question is where to put the DB mock as there are multiple tests and every test needs a different mock result for the findOne() call.
Maybe it is possible to create a function, which gets called with the needed parameter or something like that.
First I just want to note that testing this proof-of-concept function as-is appears low in value. There isn't really any of your code in there; it's all calls to the DB client. The test is basically verifying that, if you mock the DB client to return null, it returns null. So you're really just testing your mock.
However, it would be useful if your function transformed the data somehow before returning it. (Although in that case I would put the transform in its own function with its own tests, leaving us back where we started.)
So I'll suggest a solution that does what you asked, and then one that will hopefully improve your code.
Solution that doesn't require changing getData() - Not Recommended:
You can create a function that returns a mock that provides a findOne() that returns whatever you specify:
// ./db-test-utils
function makeMockGetDbWithFindOneThatReturns(returnValue) {
const findOne = jest.fn(() => Promise.resolve(returnValue));
return jest.fn(() => ({
get: () => ({ findOne })
}));
}
Then in your code file, call DB.getDB.mockImplementation in beforeEach or beforeAll above each test, passing in the desired return value, like this:
import DB from './db';
jest.mock('./db');
describe('testing getThingById()', () => {
beforeAll(() => {
DB.getDB.mockImplementation(makeMockGetDbWithFindOneThatReturns(null));
});
test('should return null', async () => {
const result = await getData();
expect(result).toBeNull();
});
});
Solution that makes testing much easier across your DB-related code
This question is really exciting, because it is a wonderful illustration of the value of having each function do only one thing!
getData appears to be very small - only 3 lines plus a return statement. So at first glance it doesn't seem to be doing too much.
However, this tiny function has very tight coupling with the internal structure of DB. It has dependency on:
DB - a singleton
DB.getDB()
DB.getDB().get()
DB.getDB().get().findOne()
This has some negative repercussions:
If DB ever changes its structure, which since it uses a 3rd party component, is possible, then every function you have that has these dependencies will break.
It's very hard to test!
The code isn't reusable. So every function that accesses the DB will need to call getDB() and db.get('collection'), resulting in repeated code.
Here's one way you could improve things, while making your test mocks much simpler.
Export db instead of DB
I could be wrong, but my guess is, every time you use DB, the first thing you'll do is call getDB(). But you only ever need to make that call once in your entire codebase. Instead of repeating that code everywhere, you can export db from ./lib/db.js instead of DB:
// ./lib/db.js
const DB = existingCode(); // However you're creating DB now
const dbInstance = DB.getDB();
export default dbInstance;
Alternatively, you could create the db instance in a startup function and then pass it in to a DataAccessLayer class, which would house all of your DB access calls. Again only calling getDB() once. That way you avoid the singleton, which makes testing easier because it allows dependency injection.
Add a helper function to get the DB collection
// ./lib/db.js
const DB = existingCode(); // However you're creating DB now
const dbInstance = DB.getDB();
export function getCollectionByName(collectionName){
return dbInstance.get(collectionName);
}
export default dbInstance;
This function is so trivial it might seem unnecessary. After all, it has the same number of lines as the code it replaces! But it removes the dependency on the structure of dbInstance (previously db) from calling code, while documenting what get() does (which is not obvious from its name).
Now your getData, which I'm renaming getDocById to reflect what it actually does, can look like this:
import { getCollectionByName } from './lib/db';
export async function getDocById(id) {
const collection = getCollectionByName('things');
const doc = await collection.findOne({ _id: id })
return doc;
}
Now you can mock getCollectionByName separately from DB:
// getData.test.js
import { getDocById } from '../getData'
import { getCollectionByName } from './lib/db'
jest.mock('./lib/db');
describe('testing getThingById()', () => {
beforeEach(() => {
getCollectionByName.mockImplementation(() => ({
findOne: jest.fn(() => Promise.resolve(null))
}));
});
test('should return null', async () => {
const result = await getDocById();
expect(result).toBeNull();
});
});
This is just one approach and it could be taken much further. For example we could export findOneDocById(collectionName, id) and/or findOneDoc(collectionName, searchObject) to make both our mock and calls to findOne() simpler.

Categories