How to configure deploy_contract for fork Pancakeswap - ethereum

I woudlike to fork pancakeswap on binance smart chain :
I have actually this contracts :
Caketoken.sol
SyrupBar.sol
MasterChef.sol
Migrations.sol
Timelock.sol
How can I specify the 2_deploy_contract.js to deploy each of this token ?
const CakeToken = artifacts.require("CakeToken");
const SyrupBar = artifacts.require("SyrupBar");
const MasterChef = artifacts.require("MasterChefV2");
let admin = "adress 0X"
module.exports = function(deployer) {
// 1st deployment
deployer.deploy(CakeToken).then(function() {
return deployer.deploy(SyrupBar, CakeToken.address).then(function() {
return deployer.deploy(MasterChef, CakeToken.address, SyrupBar.address, admin, "1000000000000000000", 4021488)
})
})
};

Related

Hardhat, ether.js. TypeError: deployer.sendTransaction is not a function error. Trying to execute exactInput UniswapV3 function

Im trying to execute a swap to buy Uni using UniswapV3 interface.
It sends me this error related to the sendTransaction() function and I dont understand why as a lot of examples that I have seen use it this way.
Im using hardhat as you can see and calling the getToken() from another script and deploying on goerli network. Function to check UNI wallet balnceOf() at the end works fine.
Also what advice would you recommend me to improve my code?
This is the code:
const { ethers, getNamedAccounts, network } = require("hardhat")
const {abi: V3SwapRouterABI} = require('#uniswap/v3-periphery/artifacts/contracts/interfaces/ISwapRouter.sol/ISwapRouter.json')
const FEE_SIZE = 3
function encodePath(path, fees) {
if (path.length != fees.length + 1) {
throw new Error('path/fee lengths do not match')
}
let encoded = '0x'
for (let i = 0; i < fees.length; i++) {
// 20 byte encoding of the address
encoded += path[i].slice(2)
// 3 byte encoding of the fee
encoded += fees[i].toString(16).padStart(2 * FEE_SIZE, '0')
}
// encode the final token
encoded += path[path.length - 1].slice(2)
return encoded.toLowerCase()
}
async function getToken() {
// const signer = provider.getSigner()
const deadline = Math.floor(Date.now()/1000) + (60*10)
const wethToken= "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
const Uni= "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984"
const UniswapRouter="0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"
const path = encodePath([wethToken, Uni], [3000])
console.log(path)
const { deployer } = await getNamedAccounts()
const UniV3Contract = await ethers.getContractAt(
V3SwapRouterABI,
UniswapRouter,
deployer
)
const params = {
path: path,
recipient:deployer,
deadline: deadline,
amountIn: ethers.utils.parseEther('0.01'),
amountOutMinimum: 0
}
const encodedData = UniV3Contract.interface.encodeFunctionData("exactInput", [params])
const txArg = {
to: UniswapRouter,
from: deployer,
data: encodedData,
gasLimit: ethers.utils.hexlify(1000000)
}
const tx = await deployer.sendTransaction(txArg)
console.log('tx: ', tx)
const IUni = await ethers.getContractAt(
"IERC20",
Uni,
deployer
)
const UniBlance = await IUni.balanceOf(deployer)
console.log(`Got ${UniBlance.toString()} UNI`)
}
module.exports = { getToken }
Without using the hardhat enviorment:
const {abi: V3SwapRouterABI} = require('#uniswap/v3-periphery/artifacts/contracts/interfaces/ISwapRouter.sol/ISwapRouter.json')
const { ethers } = require("ethers")
require("dotenv").config()
const INFURA_URL_TESTNET = process.env.INFURA_URL_TESTNET
const PRIVATE_KEY = process.env.PRIVATE_KEY
const WALLET_ADDRESS = process.env.WALLET_ADDRESS
// now you can call sendTransaction
const wethToken= "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6"
const Uni= "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984"
const UniswapRouter="0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"
const UniV3Contract = new ethers.Contract(
UniswapRouter,
V3SwapRouterABI
)
const provider = new ethers.providers.JsonRpcProvider(INFURA_URL_TESTNET)
const wallet = new ethers.Wallet(PRIVATE_KEY)
const signer = wallet.connect(provider)
const FEE_SIZE = 3
function encodePath(path, fees) {
if (path.length != fees.length + 1) {
throw new Error('path/fee lengths do not match')
}
let encoded = '0x'
for (let i = 0; i < fees.length; i++) {
// 20 byte encoding of the address
encoded += path[i].slice(2)
// 3 byte encoding of the fee
encoded += fees[i].toString(16).padStart(2 * FEE_SIZE, '0')
}
// encode the final token
encoded += path[path.length - 1].slice(2)
return encoded.toLowerCase()
}
async function getToken() {
const path = encodePath([wethToken, Uni], [3000])
const deadline = Math.floor(Date.now()/1000) + (60*10)
const params = {
path: path,
recipient: WALLET_ADDRESS,
deadline: deadline,
amountIn: ethers.utils.parseEther('0.01'),
amountOutMinimum: 0
}
const encodedData = UniV3Contract.interface.encodeFunctionData("exactInput", [params])
const txArg = {
to: UniswapRouter,
from: WALLET_ADDRESS,
data: encodedData,
gasLimit: ethers.utils.hexlify(1000000)
}
const tx = await signer.sendTransaction(txArg)
console.log('tx: ', tx)
const receipt = tx.wait()
console.log('receipt: ', receipt)
}
module.exports = { getToken }
I could not find documentation for getNamedAccounts but when I check the source code, I get this signature
getNamedAccounts: () => Promise<{
[name: string]: Address;
}>;
this just returns an array of account addresses and used in hardhat.config.js
namedAccounts: {
deployer: {
default: 0, // by default take the first account as deployer
1: 0,
},
},
to send the transaction programmatically:
const { ethers } = require("ethers");
const provider = new ethers.providers.JsonRpcProvider(INFURA_TEST_URL);
const wallet = new ethers.Wallet(WALLET_SECRET);
const signer = wallet.connect(provider);
// now you can call sendTransaction
const tx = await signer.sendTransaction(txArg);

Unable to initialize OpenZeppelin Clone contract during Hardhat Test (Error: VM Exception)

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,
},
},
};

No response when transaction is submitted to sawtooth intkey TP

I am trying to set up a transaction processor with hyperledger sawtooth. I tested my TP with sawtooth 1.0 and it worked fine. But when I used sawtooth 1.1 network, my transactions are not processed. It seems like the request does not reach the TP. I then tried the intkey TP from the sdk and that also has the same problem. I matched the transaction submit process from the documentation but for no good.
Sawtooth network docker file
version: "2.1"
services:
settings-tp:
image: hyperledger/sawtooth-settings-tp:1.1
container_name: sawtooth-settings-tp-default
depends_on:
- validator
entrypoint: settings-tp -vv -C tcp://validator:4004
validator:
image: hyperledger/sawtooth-validator:1.1
container_name: sawtooth-validator-default
expose:
- 4004
ports:
- "4004:4004"
# start the validator with an empty genesis batch
entrypoint: "bash -c \"\
sawadm keygen && \
sawtooth keygen my_key && \
sawset genesis -k /root/.sawtooth/keys/my_key.priv && \
sawadm genesis config-genesis.batch && \
sawtooth-validator -vv \
--endpoint tcp://validator:8800 \
--bind component:tcp://eth0:4004 \
--bind network:tcp://eth0:8800 \
\""
rest-api:
image: hyperledger/sawtooth-rest-api:1.1
container_name: sawtooth-rest-api-default
ports:
- "8008:8008"
depends_on:
- validator
entrypoint: sawtooth-rest-api -C tcp://validator:4004 --bind rest-api:8008
Transaction processor
/**
* Copyright 2016 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ------------------------------------------------------------------------------
*/
'use strict'
const { TransactionHandler } = require('sawtooth-sdk/processor/handler')
const {
InvalidTransaction,
InternalError
} = require('sawtooth-sdk/processor/exceptions')
const crypto = require('crypto')
const cbor = require('cbor')
// Constants defined in intkey specification
const MIN_VALUE = 0
const MAX_VALUE = 4294967295
const MAX_NAME_LENGTH = 20
const _hash = (x) =>
crypto.createHash('sha512').update(x).digest('hex').toLowerCase()
const INT_KEY_FAMILY = 'intkey'
const INT_KEY_NAMESPACE = _hash(INT_KEY_FAMILY).substring(0, 6)
const _decodeCbor = (buffer) =>
new Promise((resolve, reject) =>
cbor.decodeFirst(buffer, (err, obj) => (err ? reject(err) : resolve(obj)))
)
const _toInternalError = (err) => {
let message = (err.message) ? err.message : err
throw new InternalError(message)
}
const _setEntry = (context, address, stateValue) => {
let entries = {
[address]: cbor.encode(stateValue)
}
return context.setState(entries)
}
const _applySet = (context, address, name, value) => (possibleAddressValues) => {
let stateValueRep = possibleAddressValues[address]
let stateValue
if (stateValueRep && stateValueRep.length > 0) {
stateValue = cbor.decodeFirstSync(stateValueRep)
let stateName = stateValue[name]
if (stateName) {
throw new InvalidTransaction(
`Verb is "set" but Name already in state, Name: ${name} Value: ${stateName}`
)
}
}
// 'set' passes checks so store it in the state
if (!stateValue) {
stateValue = {}
}
stateValue[name] = value
return _setEntry(context, address, stateValue)
}
const _applyOperator = (verb, op) => (context, address, name, value) => (possibleAddressValues) => {
let stateValueRep = possibleAddressValues[address]
if (!stateValueRep || stateValueRep.length === 0) {
throw new InvalidTransaction(`Verb is ${verb} but Name is not in state`)
}
let stateValue = cbor.decodeFirstSync(stateValueRep)
if (stateValue[name] === null || stateValue[name] === undefined) {
throw new InvalidTransaction(`Verb is ${verb} but Name is not in state`)
}
const result = op(stateValue[name], value)
if (result < MIN_VALUE) {
throw new InvalidTransaction(
`Verb is ${verb}, but result would be less than ${MIN_VALUE}`
)
}
if (result > MAX_VALUE) {
throw new InvalidTransaction(
`Verb is ${verb}, but result would be greater than ${MAX_VALUE}`
)
}
// Increment the value in state by value
// stateValue[name] = op(stateValue[name], value)
stateValue[name] = result
return _setEntry(context, address, stateValue)
}
const _applyInc = _applyOperator('inc', (x, y) => x + y)
const _applyDec = _applyOperator('dec', (x, y) => x - y)
class IntegerKeyHandler extends TransactionHandler {
constructor () {
super(INT_KEY_FAMILY, ['1.0'], [INT_KEY_NAMESPACE])
}
apply (transactionProcessRequest, context) {
return _decodeCbor(transactionProcessRequest.payload)
.catch(_toInternalError)
.then((update) => {
//
// Validate the update
let name = update.Name
if (!name) {
throw new InvalidTransaction('Name is required')
}
if (name.length > MAX_NAME_LENGTH) {
throw new InvalidTransaction(
`Name must be a string of no more than ${MAX_NAME_LENGTH} characters`
)
}
let verb = update.Verb
if (!verb) {
throw new InvalidTransaction('Verb is required')
}
let value = update.Value
if (value === null || value === undefined) {
throw new InvalidTransaction('Value is required')
}
let parsed = parseInt(value)
if (parsed !== value || parsed < MIN_VALUE || parsed > MAX_VALUE) {
throw new InvalidTransaction(
`Value must be an integer ` +
`no less than ${MIN_VALUE} and ` +
`no greater than ${MAX_VALUE}`)
}
value = parsed
// Determine the action to apply based on the verb
let actionFn
if (verb === 'set') {
actionFn = _applySet
} else if (verb === 'dec') {
actionFn = _applyDec
} else if (verb === 'inc') {
actionFn = _applyInc
} else {
throw new InvalidTransaction(`Verb must be set, inc, dec not ${verb}`)
}
let address = INT_KEY_NAMESPACE + _hash(name).slice(-64)
// Get the current state, for the key's address:
let getPromise = context.getState([address])
// Apply the action to the promise's result:
let actionPromise = getPromise.then(
actionFn(context, address, name, value)
)
// Validate that the action promise results in the correctly set address:
return actionPromise.then(addresses => {
if (addresses.length === 0) {
throw new InternalError('State Error!')
}
console.log(`Verb: ${verb} Name: ${name} Value: ${value}`)
})
})
}
}
module.exports = IntegerKeyHandler
SendTransaction
const {createContext, CryptoFactory} = require('sawtooth-sdk/signing')
const cbor = require('cbor')
const {createHash} = require('crypto')
const {protobuf} = require('sawtooth-sdk')
const crypto = require('crypto')
// Creating a Private Key and Signer
const context = createContext('secp256k1')
const privateKey = context.newRandomPrivateKey()
const signer = new CryptoFactory(context).newSigner(privateKey)
const _hash = (x) => crypto.createHash('sha512').update(x).digest('hex').toLowerCase()
// Encoding Your Payload
const payload = {
Verb: 'get',
Name: 'foo',
Value: null
}
const payloadBytes = cbor.encode(payload)
let familyAddr = _hash('intkey').substring(0, 6);
let nameAddr = _hash(payload.Name).slice(-64);
let addr = familyAddr + nameAddr;
console.log(addr);
// Create the Transaction Header
const transactionHeaderBytes = protobuf.TransactionHeader.encode({
familyName: 'intkey',
familyVersion: '1.0',
inputs: [addr],
outputs: [addr],
signerPublicKey: signer.getPublicKey().asHex(),
batcherPublicKey: signer.getPublicKey().asHex(),
dependencies: [],
payloadSha512: createHash('sha512').update(payloadBytes).digest('hex')
}).finish()
// Create the Transaction
const signature = signer.sign(transactionHeaderBytes)
const transaction = protobuf.Transaction.create({
header: transactionHeaderBytes,
headerSignature: signature,
payload: payloadBytes
})
// Create the BatchHeader
const transactions = [transaction]
const batchHeaderBytes = protobuf.BatchHeader.encode({
signerPublicKey: signer.getPublicKey().asHex(),
transactionIds: transactions.map((txn) => txn.headerSignature),
}).finish()
// Create the Batch
const headerSignature = signer.sign(batchHeaderBytes)
const batch = protobuf.Batch.create({
header: batchHeaderBytes,
headerSignature: headerSignature,
transactions: transactions
})
// Encode the Batch(es) in a BatchList
const batchListBytes = protobuf.BatchList.encode({
batches: [batch]
}).finish()
// Submitting Batches to the Validator
const request = require('request')
request.post({
url: 'http://localhost:8008/batches',
body: batchListBytes,
headers: {'Content-Type': 'application/octet-stream'}
}, (err, response) => {
if (err) return console.log(err)
console.log(response.body)
})
There are architectural differences in the Hyperledger Sawtooth when moving from 1.0 to 1.1. One major difference is that the consensus engine is moved outside the validator service. In your docker-compose file, there is no consensus engine component also the validator service is not listening on a port for the consensus engine.
The consensus engine drives the block creation. For example, a timer expiry event in the PoET will cause the validator to create a block, validate, broadcast to other members in the network. Also, a confirmation from the consensus engine will make the validator service to commit the block to the blockchain.
Please find an example docker-compose file with the PoET consensus engine here https://github.com/hyperledger/sawtooth-core/blob/1-1/docker/compose/sawtooth-default-poet.yaml . Additionally you may try out https://github.com/hyperledger/sawtooth-core/blob/1-1/docker/compose/sawtooth-default.yaml for local dev test.
There is an explanation in the Hyperledger Sawtooth FAQ for upgrading from 1.0 version to the version 1.1 https://sawtooth.hyperledger.org/faq/upgrade/#id1 . Please feel free to ask more questions or suggestions to update these documentation.
You can also refer to the official documentation to learn in detail for different versions here https://sawtooth.hyperledger.org/docs/.

Get list of commits between two revisions in AWS CodeCommit

How can I get a list of all commits between two revisions in AWS CodeCommit, using the SDK or AWS CLI?
Essentially, what I need is an AWS CodeCommit way to git log a..b
There's a batch-get-commits method, but that requires a list with all the commit ids.
If you call getCommit, there is a parents field in the response, which you can use to retreive the previous commit.
const AWS = require('aws-sdk')
const codecommit = new AWS.CodeCommit()
const commitIdFrom = 'commit hash'
const commitIdTo = 'commit hash'
const repositoryName = 'your repo name'
let keepRetrievingCommits = true
let commitId = commitIdTo
const commits = []
while (keepRetrievingCommits) {
try {
const params = { commitId, repositoryName }
const ccData = await codecommit.getCommit(params).promise()
const { author, message, parents } = ccData.commit
commits.push({
repositoryName,
commitId,
author: author.email,
message
})
if(parents.length === 1) {
commitId = parents[0]
}
else {
keepRetrievingCommits = false
}
if(commitId === commitIdFrom) { // won't include info of 'commitFrom'
keepRetrievingCommits = false
}
} catch(err) {
console.error(`Error while getting commit details ${commitId} on repo ${repositoryName}: ${JSON.stringify(err)}`)
}
}
// commitsData.forEach(...)

Using Sinon and Chai with ES6 constructor

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.....
}))
})