Write unit tests

This tutorial guides you through writing unit tests for the states and contracts in your CorDapp. Unit tests allow you to test the individual features of your CorDapp.

You will be creating your unit tests in these directories:

  • State tests - contracts/src/test/java/com/tutorial/contracts.
  • Contract tests - contracts/src/test/java/com/tutorial/contracts.

Learning objectives

After you’ve completed this tutorial, you will be able to write state and contract unit tests for your CorDapp.

Write state tests

This tutorial shows you how to write simple tests that check if the states have the correct parameter types. State tests can be more complex, but this example demonstrates a basic test you can implement.

You do not need to have the contract or flows created to run these state tests.

You can write all of your state tests in the same file, as shown in this tutorial, or you can create a file for each state test.

Follow these steps to write state tests:

  1. Create a file called StateTests.
  2. Add the StateTests public class.
  3. Add the @Test annotation.
  4. Add a class definition for hasFieldOfCorrectType and throw an exception called NoSuchFieldException.
  5. Fill the class with the TemplateState version of the test:
TemplateState.class.getDeclaredField("msg");
        assert (TemplateState.class.getDeclaredField("msg").getType().equals(String.class));

Your code should now look like this:

package com.tutorial.contracts;

import com.tutorial.states.TemplateState;
import org.junit.Test;

public class StateTests {

    //Mock State test check for if the state has correct parameters type
    @Test
    public void hasFieldOfCorrectType() throws NoSuchFieldException {
        TemplateState.class.getDeclaredField("msg");
        assert (TemplateState.class.getDeclaredField("msg").getType().equals(String.class));
    }

Use the template test above to create another class with tests for the parameters from the AppleStamp state:

  • stampDesc - String.
  • issuer - Party.
  • holder - Party.

Check your work

After you have finished, your state test is complete and your code should look like this:

package com.tutorial.contracts;

import com.tutorial.states.AppleStamp;
import com.tutorial.states.TemplateState;
import net.corda.core.identity.Party;
import org.junit.Test;

public class StateTests {

    //Mock State test check for if the state has correct parameters type
    @Test
    public void hasFieldOfCorrectType() throws NoSuchFieldException {
        TemplateState.class.getDeclaredField("msg");
        assert (TemplateState.class.getDeclaredField("msg").getType().equals(String.class));
    }

    @Test
    public void AppleStampStateHasFieldOfCorrectType() throws NoSuchFieldException {
        AppleStamp.class.getDeclaredField("stampDesc");
        assert (AppleStamp.class.getDeclaredField("stampDesc").getType().equals(String.class));

        AppleStamp.class.getDeclaredField("issuer");
        assert (AppleStamp.class.getDeclaredField("issuer").getType().equals(Party.class));

        AppleStamp.class.getDeclaredField("holder");
        assert (AppleStamp.class.getDeclaredField("issuer").getType().equals(Party.class));
    }

}

Write a contract test

In the contract test, you will use a feature called MockServices to fake a transaction in order to test the contract code. You must include the contract .jar with the MockServices you create. This ensures that everything from the contract folder is available for testing. To run the contract test, you must have both the contracts and states created.

Add MockServices and TestIdentity

Follow these steps to create the MockServices and mock identities to use when running the test:

  1. Create a file called ContractTests.
  2. Add the ContractTests public class.
  3. Add the private function MockServices and reference the com.tutorial.contracts .jar file.
  4. Add two TestIdentitys, giving them both X500 names.

After adding the MockServices and TestIdentitys, your code should look like this:

package com.tutorial.contracts;

import net.corda.core.identity.CordaX500Name;
import net.corda.testing.core.TestIdentity;
import net.corda.testing.node.MockServices;
import org.junit.Test;

import java.util.Arrays;

import static net.corda.testing.node.NodeTestUtils.ledger;


public class ContractTests {
    private final MockServices ledgerServices = new MockServices(Arrays.asList("com.tutorial.contracts"));
    TestIdentity alice = new TestIdentity(new CordaX500Name("Alice",  "TestLand",  "US"));
    TestIdentity bob = new TestIdentity(new CordaX500Name("Alice",  "TestLand",  "US"));

Use the template contract test to add Apple Stamp contract tests

The template CorDapp includes the following template contract test:

@Test
public void issuerAndRecipientCannotHaveSameEmail() {
    TemplateState state = new TemplateState("Hello-World",alice.getParty(),bob.getParty());
    ledger(ledgerServices, l -> {
        l.transaction(tx -> {
            tx.input(TemplateContract.ID, state);
            tx.output(TemplateContract.ID, state);
            tx.command(alice.getPublicKey(), new TemplateContract.Commands.Send());
            return tx.fails(); //fails because of having inputs
        });
        l.transaction(tx -> {
            tx.output(TemplateContract.ID, state);
            tx.command(alice.getPublicKey(), new TemplateContract.Commands.Send());
            return tx.verifies();
        });
        return null;
    });
}

Follow this example to write two tests that check:

  • That the stamp issuance can only have one output. Call this test StampIssuanceCanOnlyHaveOneOutput.
  • That the stamp has a description. Call this test StampMustHaveDescription.

Check your work

After you have completed both the StampIssuanceCanOnlyHaveOneOutput and StampMustHaveDescription tests, your contract tests are complete and your code should look like this:

package com.tutorial.contracts;

import com.tutorial.states.AppleStamp;
import com.tutorial.states.TemplateState;
import net.corda.core.contracts.UniqueIdentifier;
import net.corda.core.identity.CordaX500Name;
import net.corda.testing.core.TestIdentity;
import net.corda.testing.node.MockServices;
import org.junit.Test;

import java.util.Arrays;

import static net.corda.testing.node.NodeTestUtils.ledger;


public class ContractTests {
    private final MockServices ledgerServices = new MockServices(Arrays.asList("com.tutorial.contracts"));
    TestIdentity alice = new TestIdentity(new CordaX500Name("Alice",  "TestLand",  "US"));
    TestIdentity bob = new TestIdentity(new CordaX500Name("Alice",  "TestLand",  "US"));

    //Template Tester
    @Test
    public void issuerAndRecipientCannotHaveSameEmail() {
        TemplateState state = new TemplateState("Hello-World",alice.getParty(),bob.getParty());
        ledger(ledgerServices, l -> {
            l.transaction(tx -> {
                tx.input(TemplateContract.ID, state);
                tx.output(TemplateContract.ID, state);
                tx.command(alice.getPublicKey(), new TemplateContract.Commands.Send());
                return tx.fails(); //fails because of having inputs
            });
            l.transaction(tx -> {
                tx.output(TemplateContract.ID, state);
                tx.command(alice.getPublicKey(), new TemplateContract.Commands.Send());
                return tx.verifies();
            });
            return null;
        });
    }

    //Basket of Apple cordapp testers
    @Test
    public void StampIssuanceCanOnlyHaveOneOutput(){
        AppleStamp stamp = new AppleStamp("FUji4072", alice.getParty(),bob.getParty(),new UniqueIdentifier());
        AppleStamp stamp2 = new AppleStamp("HoneyCrispy7864", alice.getParty(),bob.getParty(),new UniqueIdentifier());

        ledger(ledgerServices, l -> {
            l.transaction(tx -> {
                tx.output(AppleStampContract.ID, stamp);
                tx.output(AppleStampContract.ID, stamp2);
                tx.command(alice.getPublicKey(), new AppleStampContract.Commands.Issue());
                return tx.fails(); //fails because of having inputs
            });
            l.transaction(tx -> {
                tx.output(AppleStampContract.ID, stamp);
                tx.command(alice.getPublicKey(), new AppleStampContract.Commands.Issue());
                return tx.verifies();
            });
            return null;
        });
    }

    @Test
    public void StampMustHaveDescription(){
        AppleStamp stamp = new AppleStamp("", alice.getParty(),bob.getParty(),new UniqueIdentifier());
        AppleStamp stamp2 = new AppleStamp("FUji4072", alice.getParty(),bob.getParty(),new UniqueIdentifier());

        ledger(ledgerServices, l -> {
            l.transaction(tx -> {
                tx.output(AppleStampContract.ID, stamp);
                tx.command(Arrays.asList(alice.getPublicKey(),bob.getPublicKey()), new AppleStampContract.Commands.Issue());
                return tx.fails(); //fails because of having inputs
            });
            l.transaction(tx -> {
                tx.output(AppleStampContract.ID, stamp2);
                tx.command(Arrays.asList(alice.getPublicKey(),bob.getPublicKey()), new AppleStampContract.Commands.Issue());
                return tx.verifies();
            });
            return null;
        });
    }


}

Next steps

Now that you know how to write unit tests, learn how to run your CorDapp then write Integration tests .

Was this page helpful?

Thanks for your feedback!

Chat with us

Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.

Propose documentation improvements directly

Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.

We're sorry this page wasn't helpful. Let us know how we can make it better!

Chat with us

Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.

Create an issue

Create a new GitHub issue in this repository - submit technical feedback, draw attention to a potential documentation bug, or share ideas for improvement and general feedback.

Propose documentation improvements directly

Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.