Skip to content

Commit

Permalink
🪖 Waffle hardhat testing (#685)
Browse files Browse the repository at this point in the history
  • Loading branch information
yivlad committed Apr 8, 2022
1 parent 3572d4f commit 25b3c79
Show file tree
Hide file tree
Showing 37 changed files with 2,215 additions and 1,359 deletions.
690 changes: 613 additions & 77 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

18 changes: 17 additions & 1 deletion waffle-chai/src/matchers/revertedWith.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ export function supportRevertedWith(Assertion: Chai.AssertionStatic) {
};

const onError = (error: any) => {
const revertString = error?.receipt?.revertString ?? decodeRevertString(error);
const revertString = error?.receipt?.revertString ??
decodeHardhatError(error) ??
decodeRevertString(error);
if (revertString !== undefined) {
const isReverted = revertReason instanceof RegExp
? revertReason.test(revertString)
Expand Down Expand Up @@ -74,3 +76,17 @@ export function supportRevertedWith(Assertion: Chai.AssertionStatic) {
return this;
});
}

const decodeHardhatError = (error: any) => {
const tryDecode = (error: any) => {
const errorString = String(error);
const regexp = new RegExp('VM Exception while processing transaction: reverted with reason string \'(.*)\'');
const matches = regexp.exec(errorString);
if (!matches) {
return undefined;
}
return matches[1];
};

return tryDecode(error) ?? tryDecode(error.error); // the error may be wrapped
};
11 changes: 11 additions & 0 deletions waffle-chai/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,14 @@ import {waffleChai} from '../src';

chai.use(chaiAsPromised);
chai.use(waffleChai);

export {calledOnContractTest} from './matchers/calledOnContract/calledOnContractTest';
export {calledOnContractValidatorsTest} from './matchers/calledOnContract/calledOnContractValidatorsTest';
export {calledOnContractWithTest} from './matchers/calledOnContract/calledOnContractWithTest';
export {changeEtherBalanceTest} from './matchers/changeEtherBalanceTest';
export {changeEtherBalancesTest} from './matchers/changeEtherBalancesTest';
export {changeTokenBalanceTest} from './matchers/changeTokenBalanceTest';
export {changeTokenBalancesTest} from './matchers/changeTokenBalancesTest';
export {eventsTest} from './matchers/eventsTest';
export {revertedTest} from './matchers/revertedTest';
export {revertedWithTest} from './matchers/revertedWithTest';
Original file line number Diff line number Diff line change
@@ -1,56 +1,6 @@
import {AssertionError, expect} from 'chai';
import {MockProvider} from '@ethereum-waffle/provider';
import {ContractFactory} from 'ethers';
import {CALLS_ABI, CALLS_BYTECODE} from '../../contracts/Calls';
import {describeMockProviderCases} from '../MockProviderCases';

async function setup(provider: MockProvider) {
const [deployer] = provider.getWallets();

const factory = new ContractFactory(CALLS_ABI, CALLS_BYTECODE, deployer);
return {contract: await factory.deploy()};
}
import {calledOnContractTest} from './calledOnContractTest';

describeMockProviderCases('INTEGRATION: calledOnContract', (provider) => {
it('checks that contract function was called', async () => {
const {contract} = await setup(provider);
await contract.callWithoutParameter();

expect('callWithoutParameter').to.be.calledOnContract(contract);
});

it('throws assertion error when contract function was not called', async () => {
const {contract} = await setup(provider);

expect(
() => expect('callWithoutParameter').to.be.calledOnContract(contract)
).to.throw(AssertionError, 'Expected contract function to be called');
});

it('checks that contract function was not called', async () => {
const {contract} = await setup(provider);

expect('callWithoutParameter').not.to.be.calledOnContract(contract);
});

it('throws assertion error when contract function was called', async () => {
const {contract} = await setup(provider);
await contract.callWithoutParameter();

expect(
() => expect('callWithoutParameter').not.to.be.calledOnContract(contract)
).to.throw(AssertionError, 'Expected contract function NOT to be called');
});

it(
'checks that contract function was called on provided contract and not called on another deploy of this contract',
async () => {
const {contract} = await setup(provider);
const {contract: secondDeployContract} = await setup(provider);
await contract.callWithoutParameter();

expect('callWithoutParameter').to.be.calledOnContract(contract);
expect('callWithoutParameter').not.to.be.calledOnContract(secondDeployContract);
}
);
calledOnContractTest(provider);
});
56 changes: 56 additions & 0 deletions waffle-chai/test/matchers/calledOnContract/calledOnContractTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {AssertionError, expect} from 'chai';
import {MockProvider} from '@ethereum-waffle/provider';
import {ContractFactory} from 'ethers';

import {CALLS_ABI, CALLS_BYTECODE} from '../../contracts/Calls';

export const calledOnContractTest = (provider: MockProvider) => {
const setup = async (provider: MockProvider) => {
const [deployer] = provider.getWallets();

const factory = new ContractFactory(CALLS_ABI, CALLS_BYTECODE, deployer);
return {contract: await factory.deploy()};
};

it('checks that contract function was called', async () => {
const {contract} = await setup(provider);
await contract.callWithoutParameter();

expect('callWithoutParameter').to.be.calledOnContract(contract);
});

it('throws assertion error when contract function was not called', async () => {
const {contract} = await setup(provider);

expect(
() => expect('callWithoutParameter').to.be.calledOnContract(contract)
).to.throw(AssertionError, 'Expected contract function to be called');
});

it('checks that contract function was not called', async () => {
const {contract} = await setup(provider);

expect('callWithoutParameter').not.to.be.calledOnContract(contract);
});

it('throws assertion error when contract function was called', async () => {
const {contract} = await setup(provider);
await contract.callWithoutParameter();

expect(
() => expect('callWithoutParameter').not.to.be.calledOnContract(contract)
).to.throw(AssertionError, 'Expected contract function NOT to be called');
});

it(
'checks that contract function was called on provided contract and not called on another deploy of this contract',
async () => {
const {contract} = await setup(provider);
const {contract: secondDeployContract} = await setup(provider);
await contract.callWithoutParameter();

expect('callWithoutParameter').to.be.calledOnContract(contract);
expect('callWithoutParameter').not.to.be.calledOnContract(secondDeployContract);
}
);
};
Original file line number Diff line number Diff line change
@@ -1,68 +1,6 @@
import {expect} from 'chai';
import {MockProvider} from '@ethereum-waffle/provider';
import {constants, Contract, ContractFactory, getDefaultProvider} from 'ethers';
import {CALLS_ABI, CALLS_BYTECODE} from '../../contracts/Calls';
import {validateMockProvider} from '../../../src/matchers/calledOnContract/calledOnContractValidators';
import {describeMockProviderCases} from '../MockProviderCases';

async function setup(provider: MockProvider) {
const [deployer] = provider.getWallets();

const factory = new ContractFactory(CALLS_ABI, CALLS_BYTECODE, deployer);
return {contract: await factory.deploy()};
}
import {calledOnContractValidatorsTest} from './calledOnContractValidatorsTest';

describeMockProviderCases('INTEGRATION: ethCalledValidators', (provider) => {
it('throws type error when the argument is not a contract', async () => {
expect(
() => expect('calledFunction').to.be.calledOnContract('invalidContract')
).to.throw(TypeError, 'argument must be a contract');
});

it('throws type error when the argument is not a provider', async () => {
const contract = new Contract(
constants.AddressZero,
[],
getDefaultProvider()
);

expect(
() => expect('calledFunction').to.be.calledOnContract(contract)
).to.throw('calledOnContract matcher requires provider that support call history');
});

it('throws type error when the provided function is not a string', async () => {
const {contract} = await setup(provider);

expect(
() => expect(12).to.be.calledOnContract(contract)
).to.throw(TypeError, 'function name must be a string');
});

it('throws type error when the provided function is not in the contract', async () => {
const {contract} = await setup(provider);

expect(
() => expect('notExistingFunction').to.be.calledOnContract(contract)
).to.throw(TypeError, 'function must exist in provided contract');
});
});

describe('UNIT: provider validation', () => {
it('No call history in provider', async () => {
expect(() => validateMockProvider(getDefaultProvider()))
.to.throw('calledOnContract matcher requires provider that support call history');
});

it('Incorrect type of call history in provider', async () => {
const provider = {callHistory: 'invalidType'};
expect(() => validateMockProvider(provider))
.to.throw('calledOnContract matcher requires provider that support call history');
});

it('Correct type of call history in provider', () => {
const provider = {callHistory: []};
expect(() => validateMockProvider(provider))
.to.not.throw();
});
calledOnContractValidatorsTest(provider);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {expect} from 'chai';
import {MockProvider} from '@ethereum-waffle/provider';
import {constants, Contract, ContractFactory, getDefaultProvider} from 'ethers';

import {CALLS_ABI, CALLS_BYTECODE} from '../../contracts/Calls';
import {validateMockProvider} from '../../../src/matchers/calledOnContract/calledOnContractValidators';

export const calledOnContractValidatorsTest = (provider: MockProvider) => {
const setup = async (provider: MockProvider) => {
const [deployer] = provider.getWallets();

const factory = new ContractFactory(CALLS_ABI, CALLS_BYTECODE, deployer);
return {contract: await factory.deploy()};
};

it('throws type error when the argument is not a contract', async () => {
expect(
() => expect('calledFunction').to.be.calledOnContract('invalidContract')
).to.throw(TypeError, 'argument must be a contract');
});

it('throws type error when the argument is not a provider', async () => {
const contract = new Contract(
constants.AddressZero,
[],
getDefaultProvider()
);

expect(
() => expect('calledFunction').to.be.calledOnContract(contract)
).to.throw('calledOnContract matcher requires provider that support call history');
});

it('throws type error when the provided function is not a string', async () => {
const {contract} = await setup(provider);

expect(
() => expect(12).to.be.calledOnContract(contract)
).to.throw(TypeError, 'function name must be a string');
});

it('throws type error when the provided function is not in the contract', async () => {
const {contract} = await setup(provider);

expect(
() => expect('notExistingFunction').to.be.calledOnContract(contract)
).to.throw(TypeError, 'function must exist in provided contract');
});

describe('UNIT: provider validation', () => {
it('No call history in provider', async () => {
expect(() => validateMockProvider(getDefaultProvider()))
.to.throw('calledOnContract matcher requires provider that support call history');
});

it('Incorrect type of call history in provider', async () => {
const provider = {callHistory: 'invalidType'};
expect(() => validateMockProvider(provider))
.to.throw('calledOnContract matcher requires provider that support call history');
});

it('Correct type of call history in provider', () => {
const provider = {callHistory: []};
expect(() => validateMockProvider(provider))
.to.not.throw();
});
});
};
Original file line number Diff line number Diff line change
@@ -1,86 +1,6 @@
import {MockProvider} from '@ethereum-waffle/provider';
import {ContractFactory} from 'ethers';
import {CALLS_ABI, CALLS_BYTECODE} from '../../contracts/Calls';
import {AssertionError, expect} from 'chai';
import {describeMockProviderCases} from '../MockProviderCases';

async function setup(provider: MockProvider) {
const [deployer] = provider.getWallets();

const factory = new ContractFactory(CALLS_ABI, CALLS_BYTECODE, deployer);
return {contract: await factory.deploy()};
}
import {calledOnContractWithTest} from './calledOnContractWithTest';

describeMockProviderCases('INTEGRATION: calledOnContractWith', (provider) => {
it('checks that contract function with provided parameter was called', async () => {
const {contract} = await setup(provider);

await contract.callWithParameter(1);

expect('callWithParameter').to.be.calledOnContractWith(contract, [1]);
});

it('checks that contract function with provided multiple parameters was called', async () => {
const {contract} = await setup(provider);

await contract.callWithParameters(2, 3);

expect('callWithParameters').to.be.calledOnContractWith(contract, [2, 3]);
});

it('throws assertion error when contract function with parameter was not called', async () => {
const {contract} = await setup(provider);

expect(
() => expect('callWithParameter').to.be.calledOnContractWith(contract, [1])
).to.throw(AssertionError, 'Expected contract function with parameters to be called');
});

it('checks that contract function with parameter was not called', async () => {
const {contract} = await setup(provider);

await contract.callWithParameter(2);

expect('callWithParameter').not.to.be.calledOnContractWith(contract, [1]);
});

it('checks that contract function with parameters was not called', async () => {
const {contract} = await setup(provider);

await contract.callWithParameters(1, 2);

expect('callWithParameters').not.to.be.calledOnContractWith(contract, [1, 3]);
});

it('throws assertion error when contract function with parameter was called', async () => {
const {contract} = await setup(provider);
await contract.callWithParameter(2);

expect(
() => expect('callWithParameter').not.to.be.calledOnContractWith(contract, [2])
).to.throw(AssertionError, 'Expected contract function with parameters NOT to be called');
});

it(
'checks that contract function was called on provided contract and not called on another deploy of this contract',
async () => {
const {contract} = await setup(provider);
const {contract: secondDeployContract} = await setup(provider);
await contract.callWithParameter(2);

expect('callWithParameter').to.be.calledOnContractWith(contract, [2]);
expect('callWithParameter').not.to.be.calledOnContractWith(secondDeployContract, [2]);
}
);

it(
'checks that contract function which was called twice with different args, lets possibility to find desirable call',
async () => {
const {contract} = await setup(provider);

await contract.callWithParameters(2, 3);
await contract.callWithParameters(4, 5);

expect('callWithParameters').to.be.calledOnContractWith(contract, [2, 3]);
});
calledOnContractWithTest(provider);
});

0 comments on commit 25b3c79

Please sign in to comment.