I'm trying to create unit tests for my class which follows:
MyService.js:
const ApiServce = require('./api-service')
const Config = require('./config')
const Redis = require('ioredis')
class MyService {
constructor () {
const self = this
self.apiService = new ApiServce('MyService', '1.0.0', Config.port)
self.registerRoutes() //this invokes self.apiSerivce.registerRoutes
self.redis = new Redis(Config.redisport, Config.redishost)
self.queueKey = Config.redisqueuekey
}
run () {
const self = this
self.apiService.run()
}
}
module.exports = MyService
Config.js
module.exports = {
port: process.env.SVC_PORT || 8070,
redishost: process.env.REDIS_HOST || '127.0.0.1',
redisport: process.env.REDIS_PORT || 6379,
redisqueuekey: process.env.REDIS_Q_KEY || 'myeventqueue'
}
Test file:
const Redis = require('ioredis')
const MyService = require('../src/myservice')
const ApiService = require('../src/api-service')
const Chai = require('chai')
const Sinon = require('sinon')
const SinonChai = require('sinon-chai')
Chai.use(SinonChai)
const should = Chai.should()
const expect = Chai.expect
describe('MyService', function () {
let apiservicestub, redisstub, apiconststub
beforeEach(function () {
apiservicestub = Sinon.stub(ApiService.prototype, 'registerRoutes')
redisstub = Sinon.stub(Redis.prototype, 'connect')
redisstub.returns(Promise.resolve())
})
describe('.constructor', function () {
it('creates instances of api service and redis client with correct parameters', Sinon.test(function () {
try {
const service = new MyService()
expect(apiservicestub).called
expect(redisstub).called
} catch (e) {
console.error(e)
expect(false)
}
}))
Questions, Issues:
I actually want(ed) to test that the constructors of the dependent classes (apiservice and redis) are being called with the right parameters. But I couldn't find a way so I am currently resorting to one of their methods which is not what I want.
Is there a way in Sinon to achieve this? Do I need to restructure the code to fit Sinon's requirements?
I also want to provide test values for Config items e.g. port to see if they get used. Again I couldn't find a way in Sinon to do that.
I tried the createStubInstance for both 1 and 2 as well but keep getting errors.
Any advice will be appreciated.
In order to make CommonJS modules testable without additional measures, classes should be exclusively used as properties of exports object all through the application. The classes should be destructured from module object in-place. This is not very convenient, but it works with Sinon alone.
I.e.
class ApiService {...}
exports.ApiService = ApiService;
...
const apiServiceModule = require('./api-service');
class MyService {
constructor () {
const { ApiService } = apiServiceModule;
...
In this case the properties on module objects can be mocked before MyService instantiation. Sinon spies don't support classes properly, the constructors should be wrapped:
sinon.stub(apiServiceModule, 'ApiService', function MockedApiService(...) {
return new class { constructor (...) ... };
})
Alternatively, DI can be used, and the app should be refactored according to that. Existing DI libraries (injection-js, inversify, pioc) can handle this job reasonably, but a simple DI pattern looks like this:
class MyService {
constructor (ApiService, ...) {
...
In this case all dependencies can be supplied on construction - both in application and in tests.
But most simple way is to use test-oriented packages that mess with module cache and allow to take control over require calls (rewire, proxyquire, mock-require).
Updated test file, thanks #estus for the direction:
const Redis = require('ioredis')
const ApiService = require('../src/api-service')
const Chai = require('chai')
const Sinon = require('sinon')
const SinonChai = require('sinon-chai')
const Proxyquire = require('proxyquire')
const MyService = require('../src/myservice')
Chai.use(SinonChai)
const should = Chai.should()
const expect = Chai.expect
var namespace = {
apiServiceStubClass: function () {
},
redisStubClass: function () {
}
}
describe('MyService', function () {
let ProxiedMyService
let apiservicestub, redisstub, regroutestub, configstub, apiserviceregroutes, ioredisstub
beforeEach(function () {
apiservicestub = Sinon.stub(namespace, 'apiServiceStubClass')
redisstub = Sinon.stub(namespace, 'redisStubClass')
configstub = {
version: 'testversion',
port: 9999,
redishost: 'testhost',
redisport: 9999,
redisrteventqueuekey: 'testqueyekey'
}
ProxiedMyService = Proxyquire('../src/myservice', {
'./api-service': apiservicestub,
'./config': configstub,
'ioredis': redisstub
})
regroutestub = Sinon.stub(ProxiedMyService.prototype, 'registerRoutes')
regroutestub.returns(true)
apiserviceregroutes = Sinon.stub(ApiService.prototype, 'registerRoutes')
regroutestub.returns(true)
ioredisstub = Sinon.stub(Redis.prototype, 'connect')
ioredisstub.returns(Promise.resolve())
})
afterEach(function () {
namespace.apiServiceStubClass.restore()
namespace.redisStubClass.restore()
ProxiedMyService.prototype.registerRoutes.restore()
ApiService.prototype.registerRoutes.restore()
Redis.prototype.connect.restore()
})
describe('.constructor', function () {
it('creates instances of api service and redis client with correct parameters', Sinon.test(function () {
const service = new ProxiedMyService()
expect(apiservicestub).to.have.been.calledWithNew
expect(apiservicestub).to.have.been.calledWith('MyService', 'testversion', 9999)
expect(regroutestub).to.have.been.called
expect(redisstub).to.have.been.calledWithNew
expect(redisstub).to.have.been.calledWith(9999, 'testhost')
expect(service.queueKey).to.be.equal('testqueyekey')
}))
it('creates redis client using host only when port is -1', Sinon.test(function () {
configstub.redisport = -1
const service = new ProxiedMyService()
expect(redisstub).to.have.been.calledWith('testhost')
}))
})
describe('.registerRoutes', function () {
it('calls apiService registerRoutes with correct url and handler', Sinon.test(function () {
const service = new MyService()
expect.....
}))
})
Related
I use openlayers js library version 6.15.1
I have a class that inherits VectorSource. I don't know what to do in my constructor cause I would like my own stategy. I can't call super({ strategy: this._myStrategy, ... }) so how to do it?
Can I add the function setStrategy in VectorSource prototype? Or is there a better solution?
You could wrap the this for the custom strategy function inside another function which is valid in super
class CustomVectorSource extends VectorSource {
constructor(options) {
options = options || {};
const strategy = options.strategy;
options.strategy = function (extent, resolution) {
if (typeof this.customStrategy_ !== 'function') {
return [];
}
return this.customStrategy_(extent, resolution);
};
super(options);
this.customStrategy_ = strategy;
}
setStrategy(strategy) {
this.customStrategy_ = strategy;
}
}
https://codesandbox.io/s/vector-wfs-forked-08vw80?file=/main.js
I'm trying to create clones of my implementation contract (EIP-1167) using the OpenZeppelin Clones library, however I keep getting a VM Exception Error.
This is the 'Initialize' function of my Implementation contract:
contract Whoopy is Initializable, VRFConsumerBaseV2, KeeperCompatible {
function initialize(address _whoopyCreator) public initializer {
VRFConsumerBaseV2.initialise(vrfCoordinatorV2);
i_vrfCoordinator = VRFCoordinatorV2Interface(vrfCoordinatorV2);
s_raffleState = RaffleState.CLOSED;
s_lastTimeStamp = block.timestamp;
whoopyCreator = _whoopyCreator;
i_vrfCoordinator.addConsumer(subscriptionId, address(this));
emit NewConsumerAdded(address(this));
}
This is my CloneFactory:
pragma solidity ^0.8.8;
import "#openzeppelin/contracts/proxy/Clones.sol";
contract WhoopyFactory {
address public implementationContract;
address[] public allClones;
event NewClone(address indexed _instance);
mapping(address => address) public whoopyList;
constructor(address _implementation) {
implementationContract = _implementation;
}
function createClone(address _whoopyCreator) payable external returns (address instance) {
instance = Clones.clone(implementationContract);
(bool success, ) = instance.call{value: msg.value}(abi.encodeWithSignature("initialize(address)", _whoopyCreator));
require(success==true, "initialize did not return true");
allClones.push(instance);
whoopyList[_whoopyCreator] = instance;
emit NewClone(instance);
return instance;
}
This is the test I am running:
const { assert, expect } = require("chai")
const { ethers, upgrades } = require("hardhat")
const { networkConfig } = require("../../helper-hardhat-config")
const fs = require("fs")
const path = require("path")
const { web3, Contract } = require("web3")
const getGas = async (tx) => {
const receipt = await ethers.provider.getTransactionReceipt(tx.hash)
return receipt.gasUsed.toString()
}
let whoopy,
Whoopy,
WhoopyFactory,
wf,
whoopyStandaloneGas,
whoopyFactoryGas,
clonedContract,
accountConnectedClone,
wCreator,
wallet,
addr1,
addr2
describe("Whoopy + WhoopyFactory", function () {
it("Initialises contract correctly", async function () {
provider = ethers.provider
addr1 = new ethers.Wallet("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", provider)
addr2 = new ethers.Wallet("0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", provider)
Whoopy = await ethers.getContractFactory("Whoopy", addr1)
whoopy = await Whoopy.deploy()
await whoopy.deployed()
const dir = path.resolve(
__dirname,
"/Users/boss/hardhat-smartcontract-lottery/artifacts/contracts/Whoopy.sol/Whoopy.json"
)
const file = fs.readFileSync(dir, "utf8")
const json = JSON.parse(file)
const abi = json.abi
WhoopyFactory = await ethers.getContractFactory("WhoopyFactory", addr1)
wf = await WhoopyFactory.deploy(whoopy.address)
await wf.deployed()
wf.connect(addr2)
const tx = await wf.createClone("0x70997970C51812dc3A010C7d01b50e0d17dc79C8", {
value: ethers.utils.parseUnits("1", "ether"),
})
console.log(tx)
const txReceipt = await tx.wait(1)
console.log(txReceipt)
})
})
When I run this test, I get the following error:
Error: VM Exception while processing transaction: reverted with reason string 'initialize did not return true'
This exact same code works perfectly when I try it in Remix, so I assume the issue has something to do with Hardhat.
Does anyone have any idea where the issue is? I've been racking my head for days but can't seem to figure out. I'd be extremely grateful if someone can point me in the right direction.
Thanks!
NOTE: Here is my hardhat config (in case it makes a difference):
require("#nomiclabs/hardhat-waffle")
require("#nomiclabs/hardhat-etherscan")
require("hardhat-deploy")
require("solidity-coverage")
require("hardhat-gas-reporter")
require("hardhat-contract-sizer")
require("dotenv").config()
require("#nomiclabs/hardhat-ethers");
// require('#openzeppelin/contracts');
const RINKEBY_RPC_URL = process.env.RINKEBY_RPC_URL
const PRIVATE_KEY = process.env.PRIVATE_KEY
const COINMARKETCAP_API_KEY = process.env.COINMARKETCAP_API_KEY
const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY
/** #type import('hardhat/config').HardhatUserConfig */
module.exports = {
defaultNetwork: "hardhat",
networks: {
hardhat: {
chainId: 31337,
blockConfirmations: 1
},
localhost: {
chainId: 31337,
},
rinkeby: {
chainId: 4,
blockConfirmations: 6,
url: RINKEBY_RPC_URL,
accounts: [PRIVATE_KEY]
}
},
solidity: "0.8.8",
gasReporter: {
enabled: true,
currency: "USD",
outputFile: "gas-report.txt",
noColors: true,
// coinmarketcap: process.env.COINMARKETCAP_API_KEY,
},
namedAccounts: {
deployer: {
default: 0,
},
player: {
default: 1,
}
},
mocha: {
timeout: 900000,
},
etherscan: {
apiKey: {
rinkeby: ETHERSCAN_API_KEY,
// kovan: ETHERSCAN_API_KEY,
// polygon: POLYGONSCAN_API_KEY,
},
},
};
I've just started using feathers to build REST server. I need your help for querying tips. Document says
When used via REST URLs all query values are strings. Depending on the service the values in params.query might have to be converted to the right type in a before hook. (https://docs.feathersjs.com/api/databases/querying.html)
, which puzzles me. find({query: {value: 1} }) does mean value === "1" not value === 1 ? Here is example client side code which puzzles me:
const feathers = require('#feathersjs/feathers')
const fetch = require('node-fetch')
const restCli = require('#feathersjs/rest-client')
const rest = restCli('http://localhost:8888')
const app = feathers().configure(rest.fetch(fetch))
async function main () {
const Items = app.service('myitems')
await Items.create( {name:'one', value:1} )
//works fine. returns [ { name: 'one', value: 1, id: 0 } ]
console.log(await Items.find({query:{ name:"one" }}))
//wow! no data returned. []
console.log(await Items.find({query:{ value:1 }})) // []
}
main()
Server side code is here:
const express = require('#feathersjs/express')
const feathers = require('#feathersjs/feathers')
const memory = require('feathers-memory')
const app = express(feathers())
.configure(express.rest())
.use(express.json())
.use(express.errorHandler())
.use('myitems', memory())
app.listen(8888)
.on('listening',()=>console.log('listen on 8888'))
I've made hooks, which works all fine but it is too tidious and I think I missed something. Any ideas?
Hook code:
app.service('myitems').hooks({
before: { find: async (context) => {
const value = context.params.query.value
if (value) context.params.query.value = parseInt(value)
return context
}
}
})
This behaviour depends on the database and ORM you are using. Some that have a schema (like feathers-mongoose, feathers-sequelize and feathers-knex), will convert values like that automatically.
Feathers itself does not know about your data format and most adapters (like the feathers-memory you are using here) do a strict comparison so they will have to be converted. The usual way to deal with this is to create some reusable hooks (instead of one for each field) like this:
const queryToNumber = (...fields) => {
return context => {
const { params: { query = {} } } = context;
fields.forEach(field => {
const value = query[field];
if(value) {
query[field] = parseInt(value, 10)
}
});
}
}
app.service('myitems').hooks({
before: {
find: [
queryToNumber('age', 'value')
]
}
});
Or using something like JSON schema e.g. through the validateSchema common hook.
Consider this scenario:
app loads => fetches json from api => needs to modify json returned
In this case, I'm using moment to make some date modifications and do some grouping that I'll use in the UI. I looked on stack and found a similar question but didn't feel like it provided the clarity I am seeking.
Where should I use .map to create the new objects that contain the formatted & grouped dates? Should I manipulate the raw json in the api call or in the redux action before I dispatch? What is the best practice?
Is it OK to add properties and mutate the object as I am showing below,
service["mStartDate"] = mStartDate before I put the data into my store and treat it as immutable state?
First Approach - changing raw json in the api call
class TicketRepository extends BaseRepository {
getDataByID(postData) {
return this.post('api/lookup', postData)
.then(result => {
const groupedData = {}
return result.map(ticket => {
const mStartDate = moment(ticket.startDate)
const mEndDate = moment(ticket.endDate)
const serviceLength = mStartDate.diff(mEndDate,'hours')
const duration = moment.duration(serviceLength,"hours").humanize()
const weekOfYear = mStartDate.format('WW')
const dayOfWeek = mStartDate.format("d")
if(!groupedData.hasOwnProperty(weekOfYear)){
groupedData[weekOfYear] = {}
}
if (!groupedData[weekOfYear].hasOwnProperty(dayOfWeek)) {
groupedData[weekOfYear][dayOfWeek] = []
}
service["mStartDate"] = mStartDate
service["mEndDate"] = mEndDate
service["serviceLength"] = serviceLength
service["duration"] = duration
groupedData[weekOfYear][dayOfWeek].push(service)
})
})
}
}
2nd Approach, make a simple api call
class TicketRepository extends BaseRepository {
getDataByID(postData) {
return this.post('api/lookup', postData)
}
}
Change the json in the action before dispatching
export function getDataByID() {
return (dispatch, getState) => {
dispatch(dataLookupRequest())
const state = getState()
const groupedData = {}
return TicketRepository.getDataByID(userData)
.then(result => {
const groupedData = {}
return result.map(ticket => {
const mStartDate = moment(ticket.startDate)
const mEndDate = moment(ticket.endDate)
const serviceLength = mStartDate.diff(mEndDate,'hours')
const duration = moment.duration(serviceLength,"hours").humanize()
const weekOfYear = mStartDate.format('WW')
const dayOfWeek = mStartDate.format("d")
if(!groupedData.hasOwnProperty(weekOfYear)){
groupedData[weekOfYear] = {}
}
if (!groupedData[weekOfYear].hasOwnProperty(dayOfWeek)) {
groupedData[weekOfYear][dayOfWeek] = []
}
service["mStartDate"] = mStartDate
service["mEndDate"] = mEndDate
service["serviceLength"] = serviceLength
service["duration"] = duration
groupedData[weekOfYear][dayOfWeek].push(service)
})
return groupedData
})
.then(groupedData => {
dispatch(lookupSuccess(groupedData))
})
.catch(err => dispatch(dataLookupFailure(err.code, err.message)))
}
}
All data manipulation should be handled by your reducer. That is, the returned response data should be passed on to a reducer. This practice is common, because this way if there's a problem with your data, you will always know where to look - reducer. So neither of your approaches is "correct". Actions should just take some input and dispatch an object (no data manipulation).
When you want to manipulate data for 'view' purposes only, consider using reselect library, which makes it easier to handle "data views" that are composed of the existing data.
I want to have a variable in a TypeScript class that is of the type "boolean isVisible()".
How do I declare it?
How do I assign this function for another instantiated object to this variable?
How do I call this function?
ps - This seems so basic but 10 minutes of searching and I couldn't find it.
function boolfn() { return true; }
function strfn() { return 'hello world'; }
var x: () => boolean;
x = strfn; // Not OK
x = boolfn; // OK
var y = x(); // y: boolean
Here's one way of doing it, though I'll be happy to work with you to figure out exactly what you're trying to achieve.
export module Sayings {
export class Greeter {
isVisible(): boolean {
return true;
}
}
}
var greeter = new Sayings.Greeter();
var visible = greeter.isVisible();
You could also use a property instead of a function. Your original question talks about a "variable" and a "function" as if they're the same thing, but that's not necessarily the case.
export module Sayings {
export class Greeter {
isVisible: boolean = false;
}
}
var greeter = new Sayings.Greeter();
var visible = greeter.isVisible;
greeter.isVisible = true;
Or something like this maybe?
export module Sayings {
export class Greeter {
constructor(public isVisible: () => boolean) {
}
}
}
var someFunc = () => {
return false;
}
var greeter = new Sayings.Greeter(someFunc);
var visible = greeter.isVisible();