Testing
Introduction
Core is built with testing in mind. In fact, support for testing with jest
is included out of the box and a jest-preset.json
file is already set up.
Core's __tests__
directory contains 4 main test directories: e2e
, functional
, integration
and unit
.
The 4 types of tests
Unit Tests
Unit tests are tests that focus on a very small, isolated portion of your code. In fact, most unit tests probably focus on a single method.
They generally are the fastest tests in your suite. The speed at which they are executed is great but they give you no guarantee that all components work once they are stitched together, which is why need the previously mentioned integration
tests.
Integration Tests
Integration tests may test a larger portion of your code, including how several objects interact with each other or external interaction like making an HTTP request to an API endpoint.
They are generally a bit slower compared to unit tests as they test the behaviour of larger parts of an application. However they guarantee that different parts of the application are working correctly together.
Functional Tests
Functional tests launch the whole application and apply some real-life scenarios : for example creating and sending a valid transaction, expecting it to be forged in the next block.
Those tests will ensure that the application is tested from the end-users perspective.
End-to-end tests
End-to-end (e2e) tests go further by spinning up real nodes on a local network, actually running the SXP blockchain on a test network (testnet) and executing scenarios on it.
They will be most useful to verify peer-to-peer interaction and running scenarios on a real network.
Code Organization
Before all, let's see and understand how the code is organized. When you open the swipechain repository, you should see the following directory structure:
/__tests__
/docker
/packages
/plugins
/scripts
For developing and testing, we are mainly interested in the packages
directory as it contains the whole core code, and __tests__
directory as it contains the tests.
Have a look at the packages
directory, to see how the application is divided into different packages :
/packages/core
/packages/core-api
/packages/core-blockchain
.......
We will now dig into the typical structure of a package. So let us look at /packages/core-blockchain
as an example. It has a main folder named src
:
/packages/core-blockchain/src
This folder contains the TypeScript code before it gets compiled to JavaScript via tsc
.
Now the unit tests associated to this packages would be located in the following directory :
/__tests__/unit/core-blockchain
Now that we have an idea of how the code is organized, we can go inside the /__tests__/unit/core-blockchain
folder and see how the tests are structured.
Tests Structure
We'll keep /__tests__/unit/core-blockchain
as an example. Open the folder and you'll see something like this:
/machines
blockchain.test.ts
/mocks
blockchain.test.ts
state-machine.test.ts
state-storage.test.ts
/src
Folder
Matching the Important thing to note: except for special directories like mocks
, the directory structure matches the /src
structure. We want to keep it this way as much as possible to make it easy to identify what is being tested. If you have worked with Go this practice should be familiar.
Mocks
Most unit tests need mocks, hence the /mock
folder where general mocking is set up (this folder contains mostly basic mocks corresponding to packages dependencies, for example core-blockchain
is depending on core-container
which will be mocked here).
Then in the actual test files we will be able to use the pre-defined mocks by importing the mock
folder, but also modify or add new mocks as we need.
Utils
The main utils folder (/__tests__/utils
) is a shared library for testing. It helps keep the tests clean and remove redundancy.
Have a look at it and don't hesitate to improve. Here are some examples of what you can find :
- Network configuration files
- Network fixtures (blocks, delegates public keys and secrets)
- Generators: generate objects such as transactions
Jest Matchers
Core provides a variety of matchers for Jest that can be used in combination with expect()
.
If you plan to use them simply run yarn add @swipechain/core-jest-matchers --dev
and include them with import "@swipechain/core-jest-matchers";
on top of your tests.
Transactions
toBeTransferType()
Assert that the given value is a transfer transaction.
expect({ type: 0 }).toBeTransferType();
toBeSecondSignatureType()
Assert that the given value is a second signature registration transaction.
expect({ type: 1 }).toBeSecondSignatureType();
toBeDelegateType()
Assert that the given value is a delegate registration transaction.
expect({ type: 2 }).toBeDelegateType();
toBeVoteType()
Assert that the given value is a vote transaction.
expect({ type: 3 }).toBeVoteType();
toBeMultiSignatureType()
Assert that the given value is a multi signature registration transaction.
expect({ type: 4 }).toBeMultiSignatureType();
toBeIpfsType()
Assert that the given value is an IPFS transaction.
expect({ type: 5 }).toBeIpfsType();
toBeTimelockTransferType()
Assert that the given value is a timelock transfer transaction.
expect({ type: 6 }).toBeTimelockTransferType();
toBeMultiPaymentType()
Assert that the given value is a multi payment transaction.
expect({ type: 7 }).toBeMultiPaymentType();
toBeDelegateResignationType()
Assert that the given value is a delegate resignation transaction.
expect({ type: 8 }).toBeDelegateResignationType();
toBeTransaction()
Assert that the given value is a transaction.
expect({
version: 1,
network: 23,
type: 0,
timestamp: 35672738,
senderPublicKey:
"03d7dfe44e771039334f4712fb95ad355254f674c8f5d286503199157b7bf7c357",
fee: 10000000,
vendorFieldHex: "5449443a2030",
amount: 200000000,
expiration: 0,
recipientId: "AFzQCx5YpGg5vKMBg4xbuYbqkhvMkKfKe5",
signature:
"304502210096ec6e27176fa694638d6fff35d7a551b2ed8c479a7e03264026eea41a05edd702206c071c97d1c6cc3bfec64dfff808cb0d5dfe857803428efb80bf7717b85cb619",
vendorField: "TID: 0",
id: "a5e9e6039675563959a783fa672c0ffe65369168a1ecffa3c89bf82961d8dbad"
}).toBeTransaction();
toBeValidTransaction()
Assert that the given value is a valid transaction.
expect({
version: 1,
network: 23,
type: 0,
timestamp: 35672738,
senderPublicKey:
"03d7dfe44e771039334f4712fb95ad355254f674c8f5d286503199157b7bf7c357",
fee: 10000000,
vendorFieldHex: "5449443a2030",
amount: 200000000,
expiration: 0,
recipientId: "AFzQCx5YpGg5vKMBg4xbuYbqkhvMkKfKe5",
signature:
"304502210096ec6e27176fa694638d6fff35d7a551b2ed8c479a7e03264026eea41a05edd702206c071c97d1c6cc3bfec64dfff808cb0d5dfe857803428efb80bf7717b85cb619",
vendorField: "TID: 0",
id: "a5e9e6039675563959a783fa672c0ffe65369168a1ecffa3c89bf82961d8dbad"
}).toBeValidTransaction();
Wallets
toBeAddress()
Assert that the given value is an address.
expect("DARiJqhogp2Lu6bxufUFQQMuMyZbxjCydN").toBeAddress();
toBePublicKey()
Assert that the given value is a public key.
expect(
"022cca9529ec97a772156c152a00aad155ee6708243e65c9d211a589cb5d43234d"
).toBePublicKey();
toBeWallet()
Assert that the given value is a wallet.
expect({
address: "DQ7VAW7u171hwDW75R1BqfHbA9yiKRCBSh",
publicKey:
"0310ad026647eed112d1a46145eed58b8c19c67c505a67f1199361a511ce7860c0"
}).toBeWallet();
toBeDelegate()
Assert that the given value is a delegate wallet.
expect({
username: "swipechainxdev",
address: "DQ7VAW7u171hwDW75R1BqfHbA9yiKRCBSh",
publicKey:
"0310ad026647eed112d1a46145eed58b8c19c67c505a67f1199361a511ce7860c0"
}).toBeDelegate();
Blocks
toBeValidArrayOfBlocks()
Assert that the given value is an array containing blocks.
expect([
{
blockSignature: "",
createdAt: "",
generatorPublicKey: "",
height: "",
id: "",
numberOfTransactions: "",
payloadHash: "",
payloadLength: "",
previousBlock: "",
reward: "",
timestamp: "",
totalAmount: "",
totalFee: "",
transactions: "",
updatedAt: "",
version: ""
}
]).toBeValidArrayOfBlocks();
toBeValidBlock()
Assert that the given value is a transfer transaction.
expect({
blockSignature: "",
createdAt: "",
generatorPublicKey: "",
height: "",
id: "",
numberOfTransactions: "",
payloadHash: "",
payloadLength: "",
previousBlock: "",
reward: "",
timestamp: "",
totalAmount: "",
totalFee: "",
transactions: "",
updatedAt: "",
version: ""
}).toBeValidBlock();
Peers
toBeValidArrayOfPeers()
Assert that the given value is an array containing peers.
expect([{ ip: "", port: "" }]).toBeValidArrayOfPeers();
toBeValidPeer()
Assert that the given value is a valid peer.
expect({ ip: "", port: "" }).toBeValidPeer();
Core API
toBeApiTransaction()
Assert that the given value is a transaction from an API response.
expect({
id: "",
blockid: "",
type: "",
timestamp: "",
amount: "",
fee: "",
senderId: "",
senderPublicKey: "",
signature: "",
asset: "",
confirmations: ""
}).toBeApiTransaction();
toBePaginated()
Assert that the given value is a paginated API response.
expect({
status: 200,
headers: {},
data: {
meta: {
pageCount: "",
totalCount: "",
next: "",
previous: "",
self: "",
first: "",
last: ""
}
}
}).toBePaginated();
toBeSuccessfulResponse()
Assert that the given value is a successful API response.
expect({
status: 200,
headers: {},
data: {
meta: {
pageCount: "",
totalCount: "",
next: "",
previous: "",
self: "",
first: "",
last: ""
}
}
}).toBeSuccessfulResponse();
Guidelines for Writing Tests
utils
Folder for Common Stuff
Use For testing, we are doing a lot of common things across the packages. Let us try to use the __tests__/utils
folder as a shared library to avoid duplication.
Here are some things that are available in utils
:
- Container set up
- Testnet configuration files
- Testnet fixtures (blocks, delegates public keys and secrets)
- Generators: generate objects such as transactions
There is still a lot to improve in utils
, some things might also be outdated. Don't hesitate to make changes to improve it.
Do more than "basic" tests
When we write some new tests, generally we start by checking that the feature is working as expected in the general case, which is perfectly fine. However, please do not stop there, it is the edge cases we are worried about.
Go deeper and test it with different parameters. Ask yourself: in which case this could very well fail, such as a particular set of parameters? If I were to refactor the feature, what would I like to be tested then?
Contact Us
If you have anything to ask, suggest or want to have any talk about testing, don't hesitate to reach out to the team.
On Slack, you can contact Air1 as he is managing the tests.