I'm using Solidity/Truffle. I have two contracts called Category and Post. Post is inherited from Category (Post is Category ).
Contract Category has an array called categories. I have a function in contract Category called isCategoryExists.
When I try to call this function from contract Category, everything is ok. But when I want to call this function from contract Post, I receive false because categories.length is 0.
Category.sol:
ragma solidity >=0.4.22 <0.9.0;
import "./ICategory.sol";
/// #title Create, edit and manage categories
contract Category is ICategory {
// State variables
uint256 currentIndex;
CategoryStruct[] private categories;
mapping(address => CategoryStruct[]) categoriesByUser;
// Modifiers
modifier onlyValidInput(CategoryInputStruct memory _input) {
bytes memory errCode;
bytes memory title = bytes(_input.title);
if (title.length == 0) {
errCode = bytes("invalidTitle");
}
if (errCode.length > 0) {
revert(string(errCode));
}
_;
}
modifier onlyValidIndex(uint256 index) {
if (isCategoryExists(index)) {
_;
} else {
revert("Invalid index.");
}
}
// Constructor
constructor() {
currentIndex = categories.length;
}
// Functions
/// #notice Check if category exists
function isCategoryExists(uint256 index) public view returns (bool) {
if (index >= categories.length) {
return false;
}
return categories[index].isExist;
}
/// #notice This function creates a category.
/// #dev Before this function, the entered data is validated with onlyValidInput modifier.
function createCategory(
CategoryInputStruct memory _input,
LocationStruct memory _location
) external onlyValidInput(_input) returns (bool) {
CategoryStruct memory newCategory = CategoryStruct({
id: currentIndex,
user: msg.sender,
title: _input.title,
isExist: true
});
categories.push(newCategory);
categoriesByUser[msg.sender].push(newCategory);
emit CategoryCreated(currentIndex);
currentIndex++;
return true;
}
}
Post.sol:
pragma solidity >=0.4.22 <0.9.0;
import "../category/Category.sol";
/// #title Create, edit and manage posts
contract Post is Category {
// State variables
uint256 currentPostIndex;
struct PostStruct {
uint256 id;
address user;
string title;
string body;
uint256 categoryId;
}
struct PostInputStruct {
string title;
string body;
uint256 categoryId;
}
PostStruct[] private posts;
mapping(address => uint256[]) postIndexesByUser; // example: 0x01234 => [3, 5, 24, 112, 448]
// Modifiers
modifier onlyValidPostInput(PostInputStruct memory _input) {
bytes memory errCode;
bytes memory title = bytes(_input.title);
bytes memory body = bytes(_input.body);
uint256 categoryId = uint256(_input.categoryId);
if (title.length == 0) {
errCode = bytes("invalidTitle");
} else if (body.length == 0) {
errCode = bytes("invalidBody");
}
if (errCode.length > 0) {
revert(string(errCode));
}
_;
}
// Constructor
constructor() {
currentPostIndex = posts.length;
}
// Functions
/// #notice This function creates a post.
/// #dev Before this function, the entered data is validated with onlyValidPostInput modifier.
function createPost(PostInputStruct memory _input)
external
onlyValidPostInput(_input)
returns (bool)
{
bool isExist = isCategoryExists(_input.categoryId);
PostStruct memory newPost = PostStruct({
id: currentPostIndex,
user: msg.sender,
title: _input.title,
body: _input.body,
categoryId: _input.categoryId
});
posts.push(newPost);
postIndexesByUser[msg.sender].push(currentPostIndex);
currentPostIndex++;
return true;
}
}
I make this call from the JS test environment in Truffle.
What I guess:
I think everything resets every time I run the tests. I'm not sure yet. This is a bit strange and has wasted a few days of my time.
Category.test.js:
const Chance = require("chance");
const Category = artifacts.require("Category");
const chance = new Chance();
contract("Category", (accounts) => {
// Setup 1 account.
const accountOne = accounts[0];
const accountTwo = accounts[1];
let categoryInstance;
before(async () => {
categoryInstance = await Category.deployed();
});
it("should create a category", async () => {
const title = chance.sentence();
// Create category
const categoryCreated = await categoryInstance.createCategory([title], {
from: accountOne,
});
// Get a category by array index
const category = await categoryInstance.getCategoryByIndex(0);
assert.equal(category.id, 0, "There is no data for this index");
assert.equal(category.title, title, "title is not equal");
});
it("Should return true because a category exists", async () => {
const isExists = await categoryInstance.isCategoryExists(0, {
from: accountOne,
});
assert.equal(isExists, true, "Category exists but result is false.");
});
});
Post.test.js
const Chance = require("chance");
const Category = artifacts.require("Category");
const Post = artifacts.require("post");
const chance = new Chance();
contract("Post", (accounts) => {
// Setup 1 account.
const accountOne = accounts[0];
const accountTwo = accounts[1];
let categoryInstance;
let postInstance;
before(async () => {
// Create a sample category
categoryInstance = await Category.deployed();
const title = chance.sentence();
// Create category
const categoryCreated = await categoryInstance.createCategory(
[title, purpose, area],
[polygon],
{
from: accountOne,
}
);
postInstance = await Post.deployed();
});
it("should create a post", async () => {
// Generate sample data
const title = chance.sentence();
const body = chance.paragraph();
const thumbnail = chance.url();
const categoryId = 0;
// Create post
const postCreated = await postInstance.createPost(
[title, body, thumbnail, categoryId],
{
from: accountOne,
}
);
// Get a post by array index
const post = await postInstance.getPostByIndex(0);
assert.equal(post.id, 0, "There is no data for this index");
assert.equal(post.title, title, "title is not equal");
assert.equal(post.categoryId, categoryId, "categoryId is not equal");
});
});
The problem is that you have misunderstood the concept of inheritance. The fact that Post inherits from Category does not mean that instances of Post are sharing the state with instances of Category. What it means is that Post is also a Category, so an object/instance of Post has that same state (variables) and behavior (functions/methods) of a Category contained within itself. In your case, this actually means an object of Post has its own array categories and that one is being checked when you call createPost, and it will of course be empty as you have never added a category using that object. The only way that array can be non-empty is if you call createCategory from postInstance, not from categoryInstance.
P.S.
Just to quickly clarify what I meant by "nothing should be persisted". The test suite should be created in a way that each test is independent, no test should ever depend on the execution of some other test. One should be able to run any test from the suite on its own and have it passing. I initially thought this was the problem, as you did not share the entire code.
Related
I am trying to follow the Chainlink VRF tutorial found here: https://docs.chain.link/docs/intermediates-tutorial/ with hardhat and am running into this issue when calling the rollDice function:
Error: cannot estimate gas; transaction may fail or may require manual gas limit (error={"reason":"cannot estimate gas; transaction may fail or may require manual gas limit","code":"UNPREDICTABLE_GAS_LIMIT","method":"estimateGas","transaction":{"from":"0x014Da1D627E6ceB555975F09D26B048644382Ac6","maxPriorityFeePerGas":{"type":"BigNumber","hex":"0x9502f900"},"maxFeePerGas":{"type":"BigNumber","hex":"0x9502f90e"},"to":"0x5887946875A01D1BB79d6Fb357BceeA5A0096D2e","data":"0xdd02d9e5000000000000000000000000014da1d627e6ceb555975f09d26b048644382ac6","type":2,"accessList":null}}, tx={"data":"0xdd02d9e5000000000000000000000000014da1d627e6ceb555975f09d26b048644382ac6","to":{},"from":"0x014Da1D627E6ceB555975F09D26B048644382Ac6","type":2,"maxFeePerGas":{"type":"BigNumber","hex":"0x9502f90e"},"maxPriorityFeePerGas":{"type":"BigNumber","hex":"0x9502f900"},"nonce":{},"gasLimit":{},"chainId":{}}, code=UNPREDICTABLE_GAS_LIMIT, version=abstract-signer/5.5.0)
at Logger.makeError (/Users/matt/Desktop/hardhat/randomDay/node_modules/#ethersproject/logger/src.ts/index.ts:225:28)
at Logger.throwError (/Users/matt/Desktop/hardhat/randomDay/node_modules/#ethersproject/logger/src.ts/index.ts:237:20)
at /Users/matt/Desktop/hardhat/randomDay/node_modules/#ethersproject/abstract-signer/src.ts/index.ts:301:31
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async Promise.all (index 7)
I am able to deploy to the Kovan testnet, I was able to verify the contract, and I have sent the contract LINK tokens, but am still running into the issue. The contract can be viewed here: https://kovan.etherscan.io/address/0x7b72d80670512c87605ab8ac7e6113fda9c57de4#code
I am using version 0.8 of the Chainlink Contracts.
RandomDay.sol
pragma solidity ^0.8.9;
import "#chainlink/contracts/src/v0.8/VRFConsumerBase.sol";
contract RandomDay is VRFConsumerBase {
uint256 private constant ROLL_IN_PROGRESS = 42;
bytes32 private s_keyHash;
uint256 private s_fee;
mapping(bytes32 => address) private s_rollers;
mapping(address => uint256) private s_results;
event DiceRolled(bytes32 indexed requestId, address indexed roller);
event DiceLanded(bytes32 indexed requestId, uint256 indexed result);
constructor(address vrfCoordinator, address link, bytes32 keyHash, uint256 fee) VRFConsumerBase(vrfCoordinator, link) {
s_keyHash = keyHash;
s_fee = fee;
}
function rollDice (address roller) public returns (bytes32 requestId) {
require(LINK.balanceOf(address(this)) >= s_fee, "Not enough LINK to pay fee");
require(s_results[roller] == 0, "Already rolled");
requestId = requestRandomness(s_keyHash, s_fee);
s_rollers[requestId] = roller;
s_results[roller] = ROLL_IN_PROGRESS;
emit DiceRolled(requestId, roller);
return requestId;
}
function fulfillRandomness (bytes32 requestId, uint256 randomness) internal override {
uint256 dayOfWeek = (randomness % 7) + 1;
s_results[s_rollers[requestId]] = dayOfWeek;
emit DiceLanded(requestId, dayOfWeek);
}
function weekday (address player) public view returns (string memory) {
require(s_results[player] != 0, "Dice not rolled");
require(s_results[player] != ROLL_IN_PROGRESS, "Roll in progress");
return getWeekdayName(s_results[player]);
}
function getWeekdayName (uint256 id) private pure returns (string memory) {
string[7] memory weekdays = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
return weekdays[id - 1];
}
}
hardhat.config.js
/**
* #type import('hardhat/config').HardhatUserConfig
*/
require("#nomiclabs/hardhat-waffle")
require("#nomiclabs/hardhat-etherscan")
const ALCHEMY_API_KEY = "*************************";
const ROPSTEN_PRIVATE_KEY = "*********************";
module.exports = {
solidity: "0.8.9",
networks: {
kovan: {
url: `https://eth-kovan.alchemyapi.io/v2/${ALCHEMY_API_KEY}`,
accounts: [`0x${ROPSTEN_PRIVATE_KEY}`],
gas: 2700000000,
maxFeePerGas: 30000000000,
}
},
etherscan: {
// Your API key for Etherscan
// Obtain one at https://etherscan.io/
apiKey: "****************************"
}
};
deploy.js
const { ethers } = require("hardhat");
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying contracts with the account:", deployer.address);
console.log("Account balance:", (await deployer.getBalance()).toString());
const Token = await ethers.getContractFactory("RandomDay");
const token = await Token.deploy("0xa36085F69e2889c224210F603D836748e7dC0088", "0xdD3782915140c8f3b190B5D67eAc6dc5760C46E9", "0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f4", "100000000000000000");
console.log("Token address:", token.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
quickRun.js
var ethers = require('ethers');
var provider = ethers.providers.getDefaultProvider('kovan');
var address = '0x7b72d80670512c87605aB8aC7E6113Fda9c57de4';
var abi = [{"inputs":[{"internalType":"address","name":"vrfCoordinator","type":"address"},{"internalType":"address","name":"link","type":"address"},{"internalType":"bytes32","name":"keyHash","type":"bytes32"},{"internalType":"uint256","name":"fee","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"requestId","type":"bytes32"},{"indexed":true,"internalType":"uint256","name":"result","type":"uint256"}],"name":"DiceLanded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"requestId","type":"bytes32"},{"indexed":true,"internalType":"address","name":"roller","type":"address"}],"name":"DiceRolled","type":"event"},{"inputs":[{"internalType":"bytes32","name":"requestId","type":"bytes32"},{"internalType":"uint256","name":"randomness","type":"uint256"}],"name":"rawFulfillRandomness","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"roller","type":"address"}],"name":"rollDice","outputs":[{"internalType":"bytes32","name":"requestId","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"player","type":"address"}],"name":"weekday","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}];
var privateKey = '*******************************';
var wallet = new ethers.Wallet(privateKey, provider);
var contract = new ethers.Contract(address, abi, wallet);
var sendPromise = contract.rollDice('0x014Da1D627E6ceB555975F09D26B048644382Ac6');
sendPromise.then(function(transaction){
console.log(transaction);
});
I believe the addresses are off:
Constructor Arguments of your contract:
-----Decoded View---------------
Arg [0] : vrfCoordinator (address): 0xa36085f69e2889c224210f603d836748e7dc0088
Arg [1] : link (address): 0xdd3782915140c8f3b190b5d67eac6dc5760c46e9
Arg [2] : keyHash (bytes32): 0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f4
Arg [3] : fee (uint256): 100000000000000000
Via Chainlinks Docs:
LINK 0xa36085F69e2889c224210F603D836748e7dC0088
VRF Coordinator 0xdD3782915140c8f3b190B5D67eAc6dc5760C46E9
Key Hash 0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f4
Fee 0.1 LINK
Switched Link and VRF are probably the culprit.
9 out of 10 is because you passed something wrong in the constructor or something in the contructor is not initialized propertly. this kind of things aren't detected by compiler.
I am having trouble removing an item in a list after adding the element.
List<CartItem> _items = [];
FirebaseFirestore? _instance;
void add(BuildContext context, CartItem item) {
_items.add(item);
AuthService authService = Provider.of<AuthService>(context, listen: false);
Map<String, dynamic> cartMap = Map();
_items.forEach((CartItem item) {
cartMap['title'] = (item.product as Product).title;
cartMap['name'] = (item.product as Product).name;
});
_instance = FirebaseFirestore.instance;
_instance!
.collection('cart')
.doc(authService.getCurrentUser()) //need to get logged in account's id
.update({
'cartProduct': FieldValue.arrayUnion([cartMap])
}).then((value) {
print(_items.length);
notifyListeners();
});
}
void remove(BuildContext context, CartItem item) {
_items.remove(item);
AuthService authService = Provider.of<AuthService>(context, listen: false);
Map<String, dynamic> cartMap = Map();
cartMap['title'] = (item.product as Product).title;
cartMap['name'] = (item.product as Product).name;
_instance = FirebaseFirestore.instance;
_instance!.collection('cart').doc(authService.getCurrentUser()).update({
'cartProduct': FieldValue.arrayRemove([cartMap]),
}).then((value) {
print(_items.length);
notifyListeners();
});
}
After I do add(context, widget.product) and print _items.length, the result is 1
However, after I do remove(context, widget.product) and print _items.length, the result is still 1.
Consumer<CartService>(
builder: (context, cart, child) {
Widget renderedButton;
if (cart.isProductAddedToCart(widget.product) == false) {
renderedButton = DefaultButton(
text: "Participate",
press: () {
print(cart.isProductAddedToCart(widget.product));
cartService.add(context, CartItem(product: widget.product));
print(cart.isProductAddedToCart(widget.product));
},
);
} else {
renderedButton = DefaultButton(
text: "Delete",
press: () {
print(cart.isProductAddedToCart(widget.product));
cartService.remove(
context, CartItem(product: widget.product));
print(cart.isProductAddedToCart(widget.product));
},
);
}
return renderedButton;
As in the code above, the remove() method is supposed to remove the same item that was added to the list using the add() method.
Just update the remove() to: (only change _items.remove(item); position)
void remove(BuildContext context, CartItem item) {
AuthService authService = Provider.of<AuthService>(context, listen: false);
Map<String, dynamic> cartMap = Map();
cartMap['title'] = (item.product as Product).title;
cartMap['name'] = (item.product as Product).name;
_instance = FirebaseFirestore.instance;
_instance!.collection('cart').doc(authService.getCurrentUser()).update({
'cartProduct': FieldValue.arrayRemove([cartMap]),
}).then((value) {
/// todo check firebase collection's deletion success first
_items.remove(item);
print(_items.length);
notifyListeners();
});
}
The item you're adding to the list may not be the same instance of the item you're removing. Make sure the item object/class have equality and hashcode implementation to compare the two items properly.
If you don't have control over the object, the following approach can be an easier alternative:
_items.removeWhere((_item) => _item.property == item.property);
^ where property can be the id of the product.
Context
After adopting a waffle example I'm experiencing some difficulties in reading out the balances of the contracts that are made using a unit test in Waffle.
Test file
import {expect, use} from 'chai';
import {Contract, utils, Wallet} from 'ethers';
import {deployContract, deployMockContract, MockProvider, solidity} from 'ethereum-waffle';
import IERC20 from '../build/IERC20.json';
import AmIRichAlready from '../build/AmIRichAlready.json';
import SolveContract from '../build/SolveContract.json';
import RandomNumberConsumer from '../build/RandomNumberConsumer.json';
use(solidity);
describe('Am I Rich Already', () => {
// Declare contracts
let mockERC20: Contract;
let askRootContract: Contract;
let solveRootContract: Contract;
let vrfContract: Contract;
// Declare wallets
let mockWallet: Wallet;
let askRootWallet: Wallet;
let solveRootWallet: Wallet;
let vrfWallet: Wallet;
beforeEach(async () => {
// generate random wallets or random origin
//const [mockWallet, askRootWallet, solveRootWallet, vrfWallet] = Wallet.createRandom();
//const original = Wallet.createRandom();
// specify wallet balances
const provider = new MockProvider(
{
ganacheOptions: {
// The private key is used to generate the four respective wallet addresses.
accounts: [
{balance: '16862680000000000001', secretKey: '0x706618637b8ca922f6290ce1ecd4c31247e9ab75cf0530a0ac95c0332173d7c1'},
{balance: '16862680000000000002', secretKey: '0x706618637b8ca922f6290ce1ecd4c31247e9ab75cf0530a0ac95c0332173d7c2'},
{balance: '16862680000000000003', secretKey: '0x706618637b8ca922f6290ce1ecd4c31247e9ab75cf0530a0ac95c0332173d7c3'},
{balance: '16862680000000000004', secretKey: '0x706618637b8ca922f6290ce1ecd4c31247e9ab75cf0530a0ac95c0332173d7c4'}
]
}
}
);
[mockWallet, askRootWallet, solveRootWallet, vrfWallet] = provider.getWallets();
mockERC20 = await deployMockContract(mockWallet, IERC20.abi);
askRootContract = await deployContract(askRootWallet, AmIRichAlready, [mockERC20.address]);
solveRootContract = await deployContract(solveRootWallet, SolveContract, [mockERC20.address]);
vrfContract = await deployContract(vrfWallet, RandomNumberConsumer);
});
// custom test in AskRoot contract
it('checks askRootContract address is returned correctly', async () => {
expect(await askRootContract.getAddressThis()).to.be.equal('0x82A666453d8aa239eEBE4578E83cD0988D62c83F');
});
// custom test in AskRoot contract
it('checks askRootWallet address balance is returned correctly', async () => {
expect(await askRootContract.getAddressThisBalance()).to.be.equal(9001);
});
});
Example contract
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
// Example contract of a TestContract.
contract SolveContract {
bool forTestingPurposes; // Boolean to run test on this contract
TemplateTestContract testContract; // Create variable for the testContract which needs to be solved.
address payable owner; // Create variable for the owner which solves the test contract.
// Constructor to initialise the contract variables.
constructor(address testAddress) public payable {
testContract = TemplateTestContract(testAddress); // Initialise the testContract variable.
owner = msg.sender; // Initialise the owner of the contract to be the creator of the contract.
}
// Function to solve the testContract.
function solve() public payable returns(uint256){
testContract.differentFunctionName(owner);
return owner.balance;
}
// Example of the main function which solves the testContract.
// Calculates the squre root function.
function main(uint x) pure public returns(uint y) {
uint z = (x + 1) / 2;
y = x;
while (z < y) {
y = z;
z = (x / z + z) / 2;
}
}
// Getter function for the Ownership.
function getOwner() public view returns (address) {
return owner;
}
// Getter function for the address(this).
function getAddressThis() public view returns (address) {
return address(this);
}
// Getter function for the balance of the contract.
function getBalance() public view returns (uint) {
//return address(this).balance;
//testAddress.balance;
return owner.balance;
}
// Getter function for the forTestingPurposes boolean.
function getForTestingPurposes() public view returns (bool){
return forTestingPurposes;
}
}
// TemplateTestContract so the SolveContract knows the structure of the testContract.
abstract contract TemplateTestContract {
function differentFunctionName(address payable hunter) public virtual;
}
Test Output
1) checks askRootWallet address balance is returned correctly
AssertionError: Expected "0" to be equal 9001
at Context.it (test/AmIRichAlready.test.ts:53:76)
at process._tickCallback (internal/process/next_tick.js:68:7)
Question
How can I (set) and read out the balance of the wallet address of a particular contract in a Waffle test?
Your test script is calling function getAddressThisBalance(), but this function is not defined in the contract.
Different JSON-RPC wrappers act differently when performing calls (read-only, without transaction) to non-existing contract functions. Some return undefined, some throw an exception, and it seems that Waffle returns a value that can be typecasted to 0.
Solution:
Unify the contract function getBalance() and the JS snippet calling getAddressThisBalance(). E.g. change the JS call to getBalance().
Uncomment the line return address(this).balance; in your getBalance() contract function. This expression returns the current balance of the contract.
Sorry if this seems a dumb question. I'm learning clean architecture as dictated by Rob Martin, and I've having a tiny bit of trouble writing one of my tests.
I wrote a couple functions in a Hive repo. Here's the code
import 'package:hive/hive.dart';
import 'package:movie_browser/features/SearchMovie/domain/entities/movie_detailed_entity.dart';
abstract class HiveMovieSearchRepoAbstract {
Future<void> cacheMovieDetails(MovieDetailed movie);
Future<MovieDetailed> getCachedMovieDetails(String id);
}
// const vars to prevent misspellings
const String MOVIEDETAILSBOX = "MovieDetailedBox";
const String SEARCHBOX = "SearchBox";
class HiveMovieSearchRepo implements HiveMovieSearchRepoAbstract {
Box movieDetailsBox = Hive.box(MOVIEDETAILSBOX) ?? null;
// TODO implement searchbox
// final searchBox = Hive.box(SEARCHBOX);
Future<void> cacheMovieDetails(MovieDetailed movie) async {
/// expects a MovieDetailed to cache. Will cache that movie
movieDetailsBox ?? await _openBox(movieDetailsBox, MOVIEDETAILSBOX);
movieDetailsBox.put('${movie.id}', movie);
}
Future<MovieDetailed> getCachedMovieDetails(String id) async {
/// expects a string id as input
/// returns the MovieDetailed if cached previously
/// returns null otherwise
movieDetailsBox ?? await _openBox(movieDetailsBox, MOVIEDETAILSBOX);
return await movieDetailsBox.get('$id');
}
_openBox(Box box, String type) async {
await Hive.openBox(type);
return Hive.box(type);
}
}
I can't think of how to test this? I want two cases, one where the box is already opened, and one case where it isn't.
Specifically, it's these lines I want to test
movieDetailsBox ?? await _openBox(movieDetailsBox, MOVIEDETAILSBOX);
_openBox(Box box, String type) async {
await Hive.openBox(type);
return Hive.box(type);
}
I thought about mocking the Box object then doing something like....
when(mockHiveMovieSearchRepo.getCachedMovieDetails(some_id)).thenAnswer((_) async => object)
but wouldn't that bypass the code I want tested and always show as positive?
Thanks so much for the help
i don't know if i fully understand your question but you can try something like this
abstract class HiveMovieSearchRepoAbstract {
Future<void> cacheMovieDetails(MovieDetailed movie);
Future<MovieDetailed> getCachedMovieDetails(String id);
}
// const vars to prevent misspellings
const String MOVIEDETAILSBOX = "MovieDetailedBox";
const String SEARCHBOX = "SearchBox";
class HiveMovieSearchRepo implements HiveMovieSearchRepoAbstract {
final HiveInterface hive;
HiveMovieSearchRepo({#required this.hive});
#override
Future<void> cacheMovieDetails(MovieDetailed cacheMovieDetails) async {
/// expects a MovieDetailed to cache. Will cache that movie
try {
final moviedetailbox = await _openBox(MOVIEDETAILSBOX);
moviedetailbox.put('${movie.id}', movie);
} catch (e) {
throw CacheException();
}
}
Future<MovieDetailed> getCachedMovieDetails(String id) async {
/// expects a string id as input
/// returns the MovieDetailed if cached previously
/// returns null otherwise
try {
final moviedetailbox = await _openBox(MOVIEDETAILSBOX);
if (moviedetailbox.containsKey(boxkeyname)) {
return await movieDetailsBox.get('$id');
}
return null;
} catch (e) {
return CacheException();
}
}
Future<Box> _openBox(String type) async {
try {
final box = await hive.openBox(type);
return box;
} catch (e) {
throw CacheException();
}
}
}
And to test it you can do something like this
class MockHiveInterface extends Mock implements HiveInterface {}
class MockHiveBox extends Mock implements Box {}
void main() {
MockHiveInterface mockHiveInterface;
MockHiveBox mockHiveBox;
HiveMovieSearchRepo hiveMovieSearchRepo;
setUp(() {
mockHiveInterface = MockHiveInterface();
mockHiveBox = MockHiveBox();
hiveMovieSearchRepo = HiveMovieSearchRepo(hive: mockHiveInterface);
});
group('cacheMoviedetails', () {
test(
'should cache the movie details',
() async{
//arrange
when(mockHiveInterface.openBox(any)).thenAnswer((_) async => mockHiveBox);
//act
await hiveMovieSearchRepo.cacheMovieDetails(tcacheMovieDetails);
//assert
verify(mockHiveBox.put('${movie.id}', tmovie));
verify(mockHiveInterface.openBox("MovieDetailedBox"));
});
});
group('getLocalCitiesAndCountriesAtPage', () {
test('should when', () async {
//arrange
when(mockHiveInterface.openBox(any))
.thenAnswer((realInvocation) async => mockHiveBox);
when(mockHiveBox.get('$id'))
.thenAnswer((realInvocation) async => tmoviedetails);
//act
final result =
await hiveMovieSearchRepo.getCachedMovieDetails(tId);
//assert
verify(mockHiveInterface.openBox(any));
verify(mockHiveBox.get('page${tpage.toString()}'));
expect(result, tmoviedetails);
});
});
}
You should add some tests also for the CacheExeption().
Hope this help you.
So, I wrote this post 9 months. Stackoverflow just sent me a notification saying it's a popular question, so I'll answer it for anyone else wondering the same thing
Easy way to make this testable is change Box to an arg passed into the class, like so
abstract class ClassName {
final Box movieDetailsBox;
final Box searchBox;
ClassName({
this.moveDetailsBox,
this.searchBox,
});
}
this makes the boxes mockable and testable
You should mock the hive interface and box;
I just started playing around in Truffle and Solidity and wrote my first basic contract. I also wrote a test but it keeps failing giving me the following message:
Error: Invalid number of arguments to Solidity function
Now, the issue seems quite straight forward, I'm not pushing the right amount af arguments... Except as far as I can see I am.
This is my relevant contract code:
pragma solidity ^0.4.18;
contract FundEth {
mapping (uint => Project) _projects;
struct Project {
uint id;
uint targetWei;
uint targetBlock;
uint balanceWei;
string name;
string description;
bool payedOut;
}
function fund(uint projectId) public payable
{
_projects[projectId].balanceWei += msg.value;
}
function create(uint targetWei, uint blocks, string name, string description)
public
returns (uint)
{
Project memory p = Project({
id: ++_indexCounter,
targetWei: targetWei,
targetBlock: block.number + blocks,
balanceWei: 0,
name: name,
description: description,
payedOut: false
});
_projects[p.id] = p;
return p.id;
}
function getProjectName(uint projectId)
public
view
returns (string)
{
return "FOO";
}
function getProjectBalance(uint projectId)
public
view
returns (uint)
{
return 10000000;
}
...
}
And this is my test code:
const FundEth = artifacts.require("./FundEth.sol");
contract('FundEth', accounts => {
var _id;
var _fundEth;
it("should create a project", () => {
return FundEth.deployed()
.then(fundEth => {
_fundEth = fundEth;
return fundEth.create(1000000000000000000 /* 1 Eth */ , 5, "FOO", "We want to fund this for testing.")
}).then(id => {
_id = id;
return _fundEth.getProjectName.call(_id)
}).then(name => {
assert.equal(name, "FOO", "Has not created a valid project.");
});
});
it("should fund a project", () => {
return FundEth.deployed()
.then(fundEth => {
assert.notEqual(_id, 0);
_fundEth = fundEth;
_fundEth.fund.sendTransaction(_id, { from: accounts[0], value: 10000000 }); << SEEMS TO FAIL HERE.
}).then(() => {
return _fundEth.getProjectBalance.call(_id);;
}).then(balance => {
assert.equal(balance, 10000000, "Balance of test project was not 1 ether.");
});
});
});
I know the contract is not very useful right now, but I don't see why it fails. The full error:
1) Contract: FundEth
should fund a project:
Uncaught Error: Invalid number of arguments to Solidity function
at Object.InvalidNumberOfSolidityArgs (/usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/errors.js:25:1)
at SolidityFunction.validateArgs (/usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/function.js:74:1)
at SolidityFunction.toPayload (/usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/function.js:90:1)
at SolidityFunction.sendTransaction (/usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/function.js:163:1)
at /usr/local/lib/node_modules/truffle/build/webpack:/~/truffle-contract/contract.js:135:1
at new Promise (<anonymous>)
at /usr/local/lib/node_modules/truffle/build/webpack:/~/truffle-contract/contract.js:126:1
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:118:7)
Changing the line to
return _fundEth.fund(_id, { from: accounts[0], value: 10000000 });
seems to fix the issue. However I also needed to remove the assert before the call in order to have a working test.