How to use JS/TS PocketIC to test interactions between 2 or more canisters?

@NathanosDev , I am using Pocket IC with JS/TS to implement a test suite for my Motoko project

I would like to use my js/ts test suite to create communication examples between 2 or more canisters. So, far I have created tests where a js client (example.psec.ts) send update/query calls to a single canister. Now I would want to start 2 or 3 canisters so that the js client sends calls to canister1 ans canister1 sends more calls to canister2/3 and so on. what is the best way to do something like that?

You can check the multicanister example for this exact scenario.

1 Like

@NathanosDev , a more specific question related to the multicanister example is how can I create a second canister that requires the principal of the first canister:

Let’s assume I want to create 2 canisters: 1) Ledger and 2) Reader which requires the principal of Ledger:

export async function TestReader(pic: PocketIc, readerCanisterId: Principal) {
  const fixture = await pic.setupCanister<TestService_reader>({
    idlFactory: TestIdlFactory_reader,
    wasm: READER_READER_WASM_PATH,
    arg: IDL.encode(init_reader({ IDL }), []), 
  });

  return fixture;
}

export async function TestLedger(pic: PocketIc, ledgerCanisterId: Principal) {
  const fixture = await pic.setupCanister<TestService_ledger>({
    idlFactory: TestIdlFactory_ledger,
    wasm: READER_LEDGER_WASM_PATH,
    arg: IDL.encode(init_ledger({ IDL }), []), 
  });

  return fixture;
}

Now I create the Ledger canister:

const fixture_ledger = await TestLedger(pic, Principal.fromText("aaaaa-aa"));
can_ledger = fixture_ledger.actor;
canCanisterId_ledger = fixture_ledger.canisterId; 

How can I now create the Reader canister and pass the Ledger principal?

You can use the init args to pass principals of other canisters, as I did in that example.

@NathanosDev
setupCanister documentation says that “Arg is a candid encoded argument to pass to the canister’s init function”.

Looking into the example, the canister code that receives the arguments seems to be a class actor with a KeyValue parameter
actor class KeyValue(phonebook : PhoneBook.Self, superheroes : SuperHeroes.Self) {

Do I need to define my canister code as a class actor with a KeyValue to be able to receive args? Is it possble for any canister defined as actor {...} to receive args?

The reason to use actor XXXX {...} instead of actor classs XXXX() {...} is because I need to access the principal of from inside this actor Principal.fromActor(XXXX). Is there a way to access the preincipal of the class actor instance from inside the class actor. Otherwise, is there a way to pass args to a non class actor?

Yes, there is a syntax for capturing the actor class’ identity:

actor class C() = c {
 /// methods can use `c`
}
2 Likes

If you want to receive the installation argument you do need to use a class.

1 Like