# Integration Tests
Make sure you have everything you need before proceeding:
- You understand the concepts of modules, keepers, and testing.
- Go is installed.
- You have the checkers blockchain codebase up to the wager payments. If not, follow the previous steps or check out the relevant version (opens new window).
In this section, you will:
- Add integration tests.
In the previous section, you handled wager payments and added unit tests that pass. You added mocks of the bank keeper. Mocks are useful to embody your expectations of the bank keeper's behavior and then quickly confirm that your code does what you expect. By interacting via the CLI, you also confirmed that the bank keeper behaved as you expected.
Now, it would be better to automatically check that your expectations of the bank keeper's behavior are correct. This is done with integration tests, and is the focus of this section.
# What is to be done
In order, you will:
- Prepare your code to accept integration tests.
- Create helper functions that will make your integration tests more succinct.
- Add integration tests that create a full app and test proper token bank balances.
As a reminder:
- At version 0.45.4 of the Cosmos SDK, an integration test creates a full app.
- At version 0.47 of the SDK, an integration test creates a minimal app, and a test that creates a full app is called an end-to-end test (E2E).
Fortunately, you do not have to do this from scratch: taking inspiration from tests on the bank module (opens new window), prepare your code so as to accommodate and create a full app that will contain a bank keeper, and add new tests.
For unit tests, each function takes a
t *testing.T (opens new window) object. For integration tests, each function will be a method on a test suite that inherits from testify's suite (opens new window). This has the advantage that your test suite can have as many fields as is necessary or useful. The objects that you have used and would welcome in the suite are:
You can spread the suite's methods to different files, so as to keep consistent naming for your test files.
go test will find the suite because you add a regular test (opens new window) that initializes the suite and runs it. The test suite is then automatically initialized with its
SetupTest (opens new window) function via its parent
suite class. After that, all the methods of the test suite are run.
# Accommodate your code
Copy and adjust from the Cosmos SDK.
Ignite CLI created a default constructor for your App with a
cosmoscmd.App (opens new window) return type, but this is not convenient. Instead of risking breaking other dependencies, add a new constructor with your
App (opens new window) as the return type.
encoding.go (opens new window) taken from here (opens new window), where you:
proto.go (opens new window) taken from here (opens new window), where you:
test_helpers.go (opens new window) taken from here (opens new window), in which you:
Initialize your checkers genesis:
Define your test suite in a new
keeper_integration_suite_test.go file in a dedicated folder
go test to it:
suite.SetupTest function, taking inspiration from the bank tests (opens new window):
SetupTest function (opens new window) is like a
beforeEach as found in other test libraries. With it, you always get a new
app in each test, without interference between them. Do not omit it (opens new window) unless you have specific reasons to do so.
It collects your
checkersModuleAddress for later use in tests that check events and balances:
# Test the test suite
You can now confirm you did all this correctly by running these new keeper integration tests, although the suite has no tests. Note how the path to call has changed:
# Helpers for money checking
Your upcoming integration tests will include checks on wagers being paid, lost, and won, so your tests need to initialize some bank balances for your players. This is made easier with a few helpers, including a helper to confirm a bank balance.
Make a bank genesis
Balance(opens new window) type from primitives:
Declare default accounts and balances that will be useful for you:
Make your preferred bank genesis state:
Add a simple function to prepare your suite with your desired balances:
Add a function to check balances from primitives:
With the preparation done, what does an integration test method look like?
# Anatomy of an integration suite test
Now you must add integration tests for your keeper in new files. What does an integration test look like? Take the example of a simple unit test (opens new window) ported to the integration test suite:
The method has a declaration:
It is declared as a member of your test suite, and is prefixed with
Test(opens new window).
The setup can be done as you prefer, but just like for unit tests you ought to create a helper and use it. Here one exists already:
The action is no different from a unit test's action, other than that you get the
msgServerfrom the suite's fields:
The verification is done with
suite.Require().X, but otherwise looks similar to the shorter
require.Xof unit tests:
In fact, it is exactly the same
require(opens new window) object.
You have added an integration test that copies an existing unit test. It demonstrates the concept but is of limited additional utility.
# Extra tests
It is time to add extra tests that check money handling by the bank. Before jumping in, as you did in play unit tests you can add a method that prepares your suite's keeper with a game ready to be played on:
You will call this function from the relevant tests.
For the tests proper, before an action that you expect to transfer money (or not), you can verify the initial position:
After the action you can test the new balances, for instance:
How you subdivide your tests and where you insert these balance checks is up to you. You can find examples here for:
- Creating a game (opens new window).
- Playing the first move (opens new window) and the second move (opens new window), including up to a resolution (opens new window). You can also check the events (opens new window).
- Failing to play a game because of a failure to pay the wager on the first move (opens new window) and the second move (opens new window).
- Forfeiting a game (opens new window), including when there has been one move played (opens new window) or two (opens new window).
# What happened to the events?
With the new tests, you may think that the events are compromised. For instance, the event type
"transfer" normally comes with three attributes, but when the bank has made two transfers the
"transfer" event ends up with 6 attributes. This is just the way events are organized: per type, with the attributes piled in.
When checking emitted events, you need to skip over the attributes you are not checking. You can easily achieve that with Go slices.
For instance, here
transferEvent.Attributes[6:] discards the first six attributes:
# Debug your suite
You learned in a previous section how to launch a test in debug mode. It is still possible to do so when using a suite. Depending on the versions of your Go installation and your Visual Studio Code, you can launch it in two ways:
Right-click on the arrow to the left of the suite's runner
In this case, you can only launch debug for all of the suite's test methods and not just a single one (as is possible with a simple test).
Right-click on the arrow to the left of the separate test of the suite:
This option may not be available. If being able to debug only a few tests at a time is important to you, a solution is to create more granular suites, for example using one or more test suites per file and falling back on the first option.
With this you have tested your wager payments in a way more realistic that unit tests and mocks.
To summarize, this section has explored:
- How to prepare your code so as to accommodate integration tests.
- How an integration test is built, and what is a test suite.
- How to add simple integration tests and helpers.
- How to add meaningful integration tests and account for how the events are emitted.
- How to debug your integration tests.