mirror of
https://github.com/corda/corda.git
synced 2025-06-16 22:28:15 +00:00
Fixed out-of-date integration testing tutorial and added Java example (#3596)
This commit is contained in:
@ -0,0 +1,124 @@
|
|||||||
|
package net.corda.docs;
|
||||||
|
|
||||||
|
import net.corda.client.rpc.CordaRPCClient;
|
||||||
|
import net.corda.core.concurrent.CordaFuture;
|
||||||
|
import net.corda.core.contracts.Amount;
|
||||||
|
import net.corda.core.contracts.Issued;
|
||||||
|
import net.corda.core.contracts.Structures;
|
||||||
|
import net.corda.core.messaging.CordaRPCOps;
|
||||||
|
import net.corda.core.node.services.Vault;
|
||||||
|
import net.corda.core.utilities.OpaqueBytes;
|
||||||
|
import net.corda.finance.contracts.asset.Cash;
|
||||||
|
import net.corda.finance.flows.CashIssueAndPaymentFlow;
|
||||||
|
import net.corda.finance.flows.CashPaymentFlow;
|
||||||
|
import net.corda.testing.driver.DriverParameters;
|
||||||
|
import net.corda.testing.driver.NodeHandle;
|
||||||
|
import net.corda.testing.driver.NodeParameters;
|
||||||
|
import net.corda.testing.node.User;
|
||||||
|
import org.junit.Test;
|
||||||
|
import rx.Observable;
|
||||||
|
|
||||||
|
import java.util.Currency;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
import static net.corda.finance.Currencies.DOLLARS;
|
||||||
|
import static net.corda.node.services.Permissions.invokeRpc;
|
||||||
|
import static net.corda.node.services.Permissions.startFlow;
|
||||||
|
import static net.corda.testing.core.ExpectKt.expect;
|
||||||
|
import static net.corda.testing.core.ExpectKt.expectEvents;
|
||||||
|
import static net.corda.testing.core.TestConstants.ALICE_NAME;
|
||||||
|
import static net.corda.testing.core.TestConstants.BOB_NAME;
|
||||||
|
import static net.corda.testing.driver.Driver.driver;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class JavaIntegrationTestingTutorial {
|
||||||
|
@Test
|
||||||
|
public void aliceBobCashExchangeExample() {
|
||||||
|
// START 1
|
||||||
|
driver(new DriverParameters()
|
||||||
|
.withStartNodesInProcess(true)
|
||||||
|
.withExtraCordappPackagesToScan(singletonList("net.corda.finance.contracts.asset")), dsl -> {
|
||||||
|
|
||||||
|
User aliceUser = new User("aliceUser", "testPassword1", new HashSet<>(asList(
|
||||||
|
startFlow(CashIssueAndPaymentFlow.class),
|
||||||
|
invokeRpc("vaultTrack")
|
||||||
|
)));
|
||||||
|
|
||||||
|
User bobUser = new User("bobUser", "testPassword2", new HashSet<>(asList(
|
||||||
|
startFlow(CashPaymentFlow.class),
|
||||||
|
invokeRpc("vaultTrack")
|
||||||
|
)));
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<CordaFuture<NodeHandle>> nodeHandleFutures = asList(
|
||||||
|
dsl.startNode(new NodeParameters().withProvidedName(ALICE_NAME).withRpcUsers(singletonList(aliceUser))),
|
||||||
|
dsl.startNode(new NodeParameters().withProvidedName(BOB_NAME).withRpcUsers(singletonList(bobUser)))
|
||||||
|
);
|
||||||
|
|
||||||
|
NodeHandle alice = nodeHandleFutures.get(0).get();
|
||||||
|
NodeHandle bob = nodeHandleFutures.get(1).get();
|
||||||
|
// END 1
|
||||||
|
|
||||||
|
// START 2
|
||||||
|
CordaRPCClient aliceClient = new CordaRPCClient(alice.getRpcAddress());
|
||||||
|
CordaRPCOps aliceProxy = aliceClient.start("aliceUser", "testPassword1").getProxy();
|
||||||
|
|
||||||
|
CordaRPCClient bobClient = new CordaRPCClient(bob.getRpcAddress());
|
||||||
|
CordaRPCOps bobProxy = bobClient.start("bobUser", "testPassword2").getProxy();
|
||||||
|
// END 2
|
||||||
|
|
||||||
|
// START 3
|
||||||
|
Observable<Vault.Update<Cash.State>> bobVaultUpdates = bobProxy.vaultTrack(Cash.State.class).getUpdates();
|
||||||
|
Observable<Vault.Update<Cash.State>> aliceVaultUpdates = aliceProxy.vaultTrack(Cash.State.class).getUpdates();
|
||||||
|
// END 3
|
||||||
|
|
||||||
|
// START 4
|
||||||
|
OpaqueBytes issueRef = OpaqueBytes.of((byte)0);
|
||||||
|
aliceProxy.startFlowDynamic(
|
||||||
|
CashIssueAndPaymentFlow.class,
|
||||||
|
DOLLARS(1000),
|
||||||
|
issueRef,
|
||||||
|
bob.getNodeInfo().getLegalIdentities().get(0),
|
||||||
|
true,
|
||||||
|
dsl.getDefaultNotaryIdentity()
|
||||||
|
).getReturnValue().get();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Class<Vault.Update<Cash.State>> cashVaultUpdateClass = (Class<Vault.Update<Cash.State>>)(Class<?>)Vault.Update.class;
|
||||||
|
|
||||||
|
expectEvents(bobVaultUpdates, true, () ->
|
||||||
|
expect(cashVaultUpdateClass, update -> true, update -> {
|
||||||
|
System.out.println("Bob got vault update of " + update);
|
||||||
|
Amount<Issued<Currency>> amount = update.getProduced().iterator().next().getState().getData().getAmount();
|
||||||
|
assertEquals(DOLLARS(1000), Structures.withoutIssuer(amount));
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
// END 4
|
||||||
|
|
||||||
|
// START 5
|
||||||
|
bobProxy.startFlowDynamic(
|
||||||
|
CashPaymentFlow.class,
|
||||||
|
DOLLARS(1000),
|
||||||
|
alice.getNodeInfo().getLegalIdentities().get(0)
|
||||||
|
).getReturnValue().get();
|
||||||
|
|
||||||
|
expectEvents(aliceVaultUpdates, true, () ->
|
||||||
|
expect(cashVaultUpdateClass, update -> true, update -> {
|
||||||
|
System.out.println("Alice got vault update of " + update);
|
||||||
|
Amount<Issued<Currency>> amount = update.getProduced().iterator().next().getState().getData().getAmount();
|
||||||
|
assertEquals(DOLLARS(1000), Structures.withoutIssuer(amount));
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
// END 5
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Exception thrown in driver DSL", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,114 +0,0 @@
|
|||||||
package net.corda.docs
|
|
||||||
|
|
||||||
import net.corda.client.rpc.CordaRPCClient
|
|
||||||
import net.corda.core.internal.concurrent.transpose
|
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
|
||||||
import net.corda.core.messaging.startFlow
|
|
||||||
import net.corda.core.messaging.vaultTrackBy
|
|
||||||
import net.corda.core.node.services.Vault
|
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
|
||||||
import net.corda.core.utilities.getOrThrow
|
|
||||||
import net.corda.finance.DOLLARS
|
|
||||||
import net.corda.finance.contracts.asset.Cash
|
|
||||||
import net.corda.finance.flows.CashIssueFlow
|
|
||||||
import net.corda.finance.flows.CashPaymentFlow
|
|
||||||
import net.corda.node.services.Permissions.Companion.invokeRpc
|
|
||||||
import net.corda.node.services.Permissions.Companion.startFlow
|
|
||||||
import net.corda.testing.core.*
|
|
||||||
import net.corda.testing.driver.DriverParameters
|
|
||||||
import net.corda.testing.driver.driver
|
|
||||||
import net.corda.testing.node.User
|
|
||||||
import org.junit.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
class IntegrationTestingTutorial {
|
|
||||||
@Test
|
|
||||||
fun `alice bob cash exchange example`() {
|
|
||||||
// START 1
|
|
||||||
driver(DriverParameters(startNodesInProcess = true,
|
|
||||||
extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset"))) {
|
|
||||||
val aliceUser = User("aliceUser", "testPassword1", permissions = setOf(
|
|
||||||
startFlow<CashIssueFlow>(),
|
|
||||||
startFlow<CashPaymentFlow>(),
|
|
||||||
invokeRpc("vaultTrackBy"),
|
|
||||||
invokeRpc(CordaRPCOps::notaryIdentities),
|
|
||||||
invokeRpc(CordaRPCOps::networkMapFeed)
|
|
||||||
))
|
|
||||||
val bobUser = User("bobUser", "testPassword2", permissions = setOf(
|
|
||||||
startFlow<CashPaymentFlow>(),
|
|
||||||
invokeRpc("vaultTrackBy"),
|
|
||||||
invokeRpc(CordaRPCOps::networkMapFeed)
|
|
||||||
))
|
|
||||||
val (alice, bob) = listOf(
|
|
||||||
startNode(providedName = ALICE_NAME, rpcUsers = listOf(aliceUser)),
|
|
||||||
startNode(providedName = BOB_NAME, rpcUsers = listOf(bobUser))
|
|
||||||
).transpose().getOrThrow()
|
|
||||||
|
|
||||||
// END 1
|
|
||||||
|
|
||||||
// START 2
|
|
||||||
val aliceClient = CordaRPCClient(alice.rpcAddress)
|
|
||||||
val aliceProxy = aliceClient.start("aliceUser", "testPassword1").proxy
|
|
||||||
|
|
||||||
val bobClient = CordaRPCClient(bob.rpcAddress)
|
|
||||||
val bobProxy = bobClient.start("bobUser", "testPassword2").proxy
|
|
||||||
// END 2
|
|
||||||
|
|
||||||
// START 3
|
|
||||||
val bobVaultUpdates = bobProxy.vaultTrackBy<Cash.State>().updates
|
|
||||||
val aliceVaultUpdates = aliceProxy.vaultTrackBy<Cash.State>().updates
|
|
||||||
// END 3
|
|
||||||
|
|
||||||
// START 4
|
|
||||||
val issueRef = OpaqueBytes.of(0)
|
|
||||||
val notaryParty = aliceProxy.notaryIdentities().first()
|
|
||||||
(1..10).map { i ->
|
|
||||||
aliceProxy.startFlow(::CashIssueFlow,
|
|
||||||
i.DOLLARS,
|
|
||||||
issueRef,
|
|
||||||
notaryParty
|
|
||||||
).returnValue
|
|
||||||
}.transpose().getOrThrow()
|
|
||||||
// We wait for all of the issuances to run before we start making payments
|
|
||||||
(1..10).map { i ->
|
|
||||||
aliceProxy.startFlow(::CashPaymentFlow,
|
|
||||||
i.DOLLARS,
|
|
||||||
bob.nodeInfo.singleIdentity(),
|
|
||||||
true
|
|
||||||
).returnValue
|
|
||||||
}.transpose().getOrThrow()
|
|
||||||
|
|
||||||
bobVaultUpdates.expectEvents {
|
|
||||||
parallel(
|
|
||||||
(1..10).map { i ->
|
|
||||||
expect(
|
|
||||||
match = { update: Vault.Update<Cash.State> ->
|
|
||||||
update.produced.first().state.data.amount.quantity == i * 100L
|
|
||||||
}
|
|
||||||
) { update ->
|
|
||||||
println("Bob vault update of $update")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// END 4
|
|
||||||
|
|
||||||
// START 5
|
|
||||||
for (i in 1..10) {
|
|
||||||
bobProxy.startFlow(::CashPaymentFlow, i.DOLLARS, alice.nodeInfo.singleIdentity()).returnValue.getOrThrow()
|
|
||||||
}
|
|
||||||
|
|
||||||
aliceVaultUpdates.expectEvents {
|
|
||||||
sequence(
|
|
||||||
(1..10).map { i ->
|
|
||||||
expect { update: Vault.Update<Cash.State> ->
|
|
||||||
println("Alice got vault update of $update")
|
|
||||||
assertEquals(update.produced.first().state.data.amount.quantity, i * 100L)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// END 5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,97 @@
|
|||||||
|
package net.corda.docs
|
||||||
|
|
||||||
|
import net.corda.client.rpc.CordaRPCClient
|
||||||
|
import net.corda.core.contracts.Amount
|
||||||
|
import net.corda.core.contracts.Issued
|
||||||
|
import net.corda.core.contracts.withoutIssuer
|
||||||
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
|
import net.corda.core.messaging.startFlow
|
||||||
|
import net.corda.core.messaging.vaultTrackBy
|
||||||
|
import net.corda.core.node.services.Vault
|
||||||
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
|
import net.corda.core.utilities.getOrThrow
|
||||||
|
import net.corda.finance.DOLLARS
|
||||||
|
import net.corda.finance.contracts.asset.Cash
|
||||||
|
import net.corda.finance.flows.CashIssueAndPaymentFlow
|
||||||
|
import net.corda.finance.flows.CashPaymentFlow
|
||||||
|
import net.corda.node.services.Permissions.Companion.invokeRpc
|
||||||
|
import net.corda.node.services.Permissions.Companion.startFlow
|
||||||
|
import net.corda.testing.core.*
|
||||||
|
import net.corda.testing.driver.DriverParameters
|
||||||
|
import net.corda.testing.driver.driver
|
||||||
|
import net.corda.testing.node.User
|
||||||
|
import org.junit.Test
|
||||||
|
import rx.Observable
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class KotlinIntegrationTestingTutorial {
|
||||||
|
@Test
|
||||||
|
fun `alice bob cash exchange example`() {
|
||||||
|
// START 1
|
||||||
|
driver(DriverParameters(
|
||||||
|
startNodesInProcess = true,
|
||||||
|
extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset")
|
||||||
|
)) {
|
||||||
|
val aliceUser = User("aliceUser", "testPassword1", permissions = setOf(
|
||||||
|
startFlow<CashIssueAndPaymentFlow>(),
|
||||||
|
invokeRpc("vaultTrackBy")
|
||||||
|
))
|
||||||
|
|
||||||
|
val bobUser = User("bobUser", "testPassword2", permissions = setOf(
|
||||||
|
startFlow<CashPaymentFlow>(),
|
||||||
|
invokeRpc("vaultTrackBy")
|
||||||
|
))
|
||||||
|
|
||||||
|
val (alice, bob) = listOf(
|
||||||
|
startNode(providedName = ALICE_NAME, rpcUsers = listOf(aliceUser)),
|
||||||
|
startNode(providedName = BOB_NAME, rpcUsers = listOf(bobUser))
|
||||||
|
).map { it.getOrThrow() }
|
||||||
|
// END 1
|
||||||
|
|
||||||
|
// START 2
|
||||||
|
val aliceClient = CordaRPCClient(alice.rpcAddress)
|
||||||
|
val aliceProxy: CordaRPCOps = aliceClient.start("aliceUser", "testPassword1").proxy
|
||||||
|
|
||||||
|
val bobClient = CordaRPCClient(bob.rpcAddress)
|
||||||
|
val bobProxy: CordaRPCOps = bobClient.start("bobUser", "testPassword2").proxy
|
||||||
|
// END 2
|
||||||
|
|
||||||
|
// START 3
|
||||||
|
val bobVaultUpdates: Observable<Vault.Update<Cash.State>> = bobProxy.vaultTrackBy<Cash.State>().updates
|
||||||
|
val aliceVaultUpdates: Observable<Vault.Update<Cash.State>> = aliceProxy.vaultTrackBy<Cash.State>().updates
|
||||||
|
// END 3
|
||||||
|
|
||||||
|
// START 4
|
||||||
|
val issueRef = OpaqueBytes.of(0)
|
||||||
|
aliceProxy.startFlow(::CashIssueAndPaymentFlow,
|
||||||
|
1000.DOLLARS,
|
||||||
|
issueRef,
|
||||||
|
bob.nodeInfo.singleIdentity(),
|
||||||
|
true,
|
||||||
|
defaultNotaryIdentity
|
||||||
|
).returnValue.getOrThrow()
|
||||||
|
|
||||||
|
bobVaultUpdates.expectEvents {
|
||||||
|
expect { update ->
|
||||||
|
println("Bob got vault update of $update")
|
||||||
|
val amount: Amount<Issued<Currency>> = update.produced.first().state.data.amount
|
||||||
|
assertEquals(1000.DOLLARS, amount.withoutIssuer())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// END 4
|
||||||
|
|
||||||
|
// START 5
|
||||||
|
bobProxy.startFlow(::CashPaymentFlow, 1000.DOLLARS, alice.nodeInfo.singleIdentity()).returnValue.getOrThrow()
|
||||||
|
|
||||||
|
aliceVaultUpdates.expectEvents {
|
||||||
|
expect { update ->
|
||||||
|
println("Alice got vault update of $update")
|
||||||
|
val amount: Amount<Issued<Currency>> = update.produced.first().state.data.amount
|
||||||
|
assertEquals(1000.DOLLARS, amount.withoutIssuer())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// END 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,119 +1,128 @@
|
|||||||
|
.. highlight:: kotlin
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<script type="text/javascript" src="_static/jquery.js"></script>
|
||||||
|
<script type="text/javascript" src="_static/codesets.js"></script>
|
||||||
|
|
||||||
Integration testing
|
Integration testing
|
||||||
===================
|
===================
|
||||||
|
|
||||||
Integration testing involves bringing up nodes locally and testing
|
Integration testing involves bringing up nodes locally and testing invariants about them by starting flows and inspecting
|
||||||
invariants about them by starting flows and inspecting their state.
|
their state.
|
||||||
|
|
||||||
In this tutorial we will bring up three nodes - Alice, Bob and a
|
In this tutorial we will bring up three nodes - Alice, Bob and a notary. Alice will issue cash to Bob, then Bob will send
|
||||||
notary. Alice will issue cash to Bob, then Bob will send this cash
|
this cash back to Alice. We will see how to test some simple deterministic and nondeterministic invariants in the meantime.
|
||||||
back to Alice. We will see how to test some simple deterministic and
|
|
||||||
nondeterministic invariants in the meantime.
|
|
||||||
|
|
||||||
.. note:: This example where Alice is self-issuing cash is purely for
|
.. note:: This example where Alice is self-issuing cash is purely for demonstration purposes, in reality, cash would be
|
||||||
demonstration purposes, in reality, cash would be issued by a bank
|
issued by a bank and subsequently passed around.
|
||||||
and subsequently passed around.
|
|
||||||
|
|
||||||
In order to spawn nodes we will use the Driver DSL. This DSL allows
|
In order to spawn nodes we will use the Driver DSL. This DSL allows one to start up node processes from code. It creates
|
||||||
one to start up node processes from code. It manages a network map
|
a local network where all the nodes see each other and provides safe shutting down of nodes in the background.
|
||||||
service and safe shutting down of nodes in the background.
|
|
||||||
|
|
||||||
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
|
.. container:: codeset
|
||||||
:language: kotlin
|
|
||||||
:start-after: START 1
|
|
||||||
:end-before: END 1
|
|
||||||
:dedent: 8
|
|
||||||
|
|
||||||
The above code starts three nodes:
|
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/KotlinIntegrationTestingTutorial.kt
|
||||||
|
:language: kotlin
|
||||||
|
:start-after: START 1
|
||||||
|
:end-before: END 1
|
||||||
|
:dedent: 8
|
||||||
|
|
||||||
* Alice, who has user permissions to start the ``CashIssueFlow`` and
|
.. literalinclude:: example-code/src/integration-test/java/net/corda/docs/JavaIntegrationTestingTutorial.java
|
||||||
``CashPaymentFlow`` flows
|
:language: java
|
||||||
* Bob, who only has user permissions to start the ``CashPaymentFlow``
|
:start-after: START 1
|
||||||
* A notary that offers a ``ValidatingNotaryService``. We won't connect
|
:end-before: END 1
|
||||||
to the notary directly, so there's no need to provide a ``User``
|
:dedent: 8
|
||||||
|
|
||||||
The ``startNode`` function returns a future that completes once the
|
The above code starts two nodes:
|
||||||
node is fully started. This allows starting of the nodes to be
|
|
||||||
parallel. We wait on these futures as we need the information
|
|
||||||
returned; their respective ``NodeHandles`` s.
|
|
||||||
|
|
||||||
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
|
* Alice, configured with an RPC user who has permissions to start the ``CashIssueAndPaymentFlow`` flow on it and query
|
||||||
:language: kotlin
|
Alice's vault.
|
||||||
:start-after: START 2
|
* Bob, configured with an RPC user who only has permissions to start the ``CashPaymentFlow`` and query Bob's vault.
|
||||||
:end-before: END 2
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
After getting the handles we wait for both parties to register with
|
.. note:: You will notice that we did not start a notary. This is done automatically for us by the driver - it creates
|
||||||
the network map to ensure we don't have race conditions with network
|
a notary node with the name ``DUMMY_NOTARY_NAME`` which is visible to both nodes. If you wish to customise this, for
|
||||||
map registration. Next we connect to Alice and Bob respectively from
|
example create more notaries, then specify the ``DriverParameters.notarySpecs`` parameter.
|
||||||
the test process using the test user we created. Then we establish RPC
|
|
||||||
links that allow us to start flows and query state.
|
|
||||||
|
|
||||||
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
|
The ``startNode`` function returns a ``CordaFuture`` object that completes once the node is fully started and visible on
|
||||||
:language: kotlin
|
the local network. Returning a future allows starting of the nodes to be parallel. We wait on these futures as we need
|
||||||
:start-after: START 3
|
the information returned; their respective ``NodeHandles`` s.
|
||||||
:end-before: END 3
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
We will be interested in changes to Alice's and Bob's vault, so we
|
.. container:: codeset
|
||||||
query a stream of vault updates from each.
|
|
||||||
|
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/KotlinIntegrationTestingTutorial.kt
|
||||||
|
:language: kotlin
|
||||||
|
:start-after: START 2
|
||||||
|
:end-before: END 2
|
||||||
|
:dedent: 12
|
||||||
|
|
||||||
|
.. literalinclude:: example-code/src/integration-test/java/net/corda/docs/JavaIntegrationTestingTutorial.java
|
||||||
|
:language: java
|
||||||
|
:start-after: START 2
|
||||||
|
:end-before: END 2
|
||||||
|
:dedent: 16
|
||||||
|
|
||||||
|
Next we connect to Alice and Bob from the test process using the test users we created. We establish RPC links that allow
|
||||||
|
us to start flows and query state.
|
||||||
|
|
||||||
|
.. container:: codeset
|
||||||
|
|
||||||
|
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/KotlinIntegrationTestingTutorial.kt
|
||||||
|
:language: kotlin
|
||||||
|
:start-after: START 3
|
||||||
|
:end-before: END 3
|
||||||
|
:dedent: 12
|
||||||
|
|
||||||
|
.. literalinclude:: example-code/src/integration-test/java/net/corda/docs/JavaIntegrationTestingTutorial.java
|
||||||
|
:language: java
|
||||||
|
:start-after: START 3
|
||||||
|
:end-before: END 3
|
||||||
|
:dedent: 16
|
||||||
|
|
||||||
|
We will be interested in changes to Alice's and Bob's vault, so we query a stream of vault updates from each.
|
||||||
|
|
||||||
Now that we're all set up we can finally get some cash action going!
|
Now that we're all set up we can finally get some cash action going!
|
||||||
|
|
||||||
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
|
.. container:: codeset
|
||||||
:language: kotlin
|
|
||||||
:start-after: START 4
|
|
||||||
:end-before: END 4
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
The first loop creates 10 threads, each starting a ``CashFlow`` flow
|
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/KotlinIntegrationTestingTutorial.kt
|
||||||
on the Alice node. We specify that we want to issue ``i`` dollars to
|
:language: kotlin
|
||||||
Bob, setting our notary as the notary responsible for notarising the
|
:start-after: START 4
|
||||||
created states. Note that no notarisation will occur yet as we're not
|
:end-before: END 4
|
||||||
spending any states, only creating new ones on the ledger.
|
:dedent: 12
|
||||||
|
|
||||||
We started the flows from different threads for the sake of the
|
.. literalinclude:: example-code/src/integration-test/java/net/corda/docs/JavaIntegrationTestingTutorial.java
|
||||||
tutorial, to demonstrate how to test non-determinism, which is what
|
:language: java
|
||||||
the ``expectEvents`` block does.
|
:start-after: START 4
|
||||||
|
:end-before: END 4
|
||||||
|
:dedent: 16
|
||||||
|
|
||||||
The Expect DSL allows ordering constraints to be checked on a stream
|
We start a ``CashIssueAndPaymentFlow`` flow on the Alice node. We specify that we want Alice to self-issue $1000 which is
|
||||||
of events. The above code specifies that we are expecting 10 updates
|
to be payed to Bob. We specify the default notary identity created by the driver as the notary responsible for notarising
|
||||||
to be emitted on the ``bobVaultUpdates`` stream in unspecified order
|
the created states. Note that no notarisation will occur yet as we're not spending any states, only creating new ones on
|
||||||
(this is what the ``parallel`` construct does). We specify an
|
the ledger.
|
||||||
(otherwise optional) ``match`` predicate to identify specific updates
|
|
||||||
we are interested in, which we then print.
|
|
||||||
|
|
||||||
If we run the code written so far we should see 4 nodes starting up
|
We expect a single update to Bob's vault when it receives the $1000 from Alice. This is what the ``expectEvents`` call
|
||||||
(Alice, Bob, the notary and an implicit Network Map service), then
|
is asserting.
|
||||||
10 logs of Bob receiving 1,2,...10 dollars from Alice in some unspecified
|
|
||||||
order.
|
.. container:: codeset
|
||||||
|
|
||||||
|
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/KotlinIntegrationTestingTutorial.kt
|
||||||
|
:language: kotlin
|
||||||
|
:start-after: START 5
|
||||||
|
:end-before: END 5
|
||||||
|
:dedent: 12
|
||||||
|
|
||||||
|
.. literalinclude:: example-code/src/integration-test/java/net/corda/docs/JavaIntegrationTestingTutorial.java
|
||||||
|
:language: java
|
||||||
|
:start-after: START 5
|
||||||
|
:end-before: END 5
|
||||||
|
:dedent: 16
|
||||||
|
|
||||||
Next we want Bob to send this cash back to Alice.
|
Next we want Bob to send this cash back to Alice.
|
||||||
|
|
||||||
.. literalinclude:: example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt
|
That's it! We saw how to start up several corda nodes locally, how to connect to them, and how to test some simple invariants
|
||||||
:language: kotlin
|
about ``CashIssueAndPaymentFlow`` and ``CashPaymentFlow``.
|
||||||
:start-after: START 5
|
|
||||||
:end-before: END 5
|
|
||||||
:dedent: 12
|
|
||||||
|
|
||||||
This time we'll do it sequentially. We make Bob pay 1,2,..10 dollars
|
You can find the complete test at ``example-code/src/integration-test/java/net/corda/docs/JavaIntegrationTestingTutorial.java``
|
||||||
to Alice in order. We make sure that a the ``CashFlow`` has finished
|
(Java) and ``example-code/src/integration-test/kotlin/net/corda/docs/KotlinIntegrationTestingTutorial.kt`` (Kotlin) in the
|
||||||
by waiting on ``startFlow`` 's ``returnValue``.
|
`Corda repo <https://github.com/corda/corda>`_.
|
||||||
|
|
||||||
Then we use the Expect DSL again, this time using ``sequence`` to test
|
|
||||||
for the updates arriving in the order we expect them to.
|
|
||||||
|
|
||||||
Note that ``parallel`` and ``sequence`` may be nested into each other
|
|
||||||
arbitrarily to test more complex scenarios.
|
|
||||||
|
|
||||||
That's it! We saw how to start up several corda nodes locally, how to
|
|
||||||
connect to them, and how to test some simple invariants about
|
|
||||||
``CashFlow``.
|
|
||||||
|
|
||||||
To run the complete test you can open
|
|
||||||
``example-code/src/integration-test/kotlin/net/corda/docs/IntegrationTestingTutorial.kt``
|
|
||||||
from IntelliJ and run the test, or alternatively use gradle:
|
|
||||||
|
|
||||||
.. sourcecode:: bash
|
|
||||||
|
|
||||||
# Run example-code integration tests
|
|
||||||
./gradlew docs/source/example-code:integrationTest -i
|
|
||||||
|
Reference in New Issue
Block a user