Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
leighmcculloch committed Dec 2, 2024
1 parent 560224e commit cad3b2a
Showing 1 changed file with 113 additions and 1 deletion.
114 changes: 113 additions & 1 deletion docs/build/guides/testing/mocking.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,116 @@ description: Mocking dependency contracts in tests.
sidebar_position: 2
---

See [issue #553](https://github.com/stellar/soroban-docs/issues/553) for more context.
Mocks are used in tests to exclude functionality that the test doesn't want to test. Mocks are often used when it's difficult to test against a real thing, such as an API. In Stellar contract tests mocks can be used sparingly because integration testing, that is testing with other contracts, is rather easy. See [Integration Testing] for more details.

### How to Write Tests with Mocks

The following is an example of a test that uses a mock, written to test the [increment-with-pause contract]. The contract has an `increment` function, that increases a counter value by one on every invocation. The contract depends on another contract that controls whether the increment functionality is paused.

The following tests sets up the `increment-with-pause` contract, as well as a mock pause contract, and invokes the increment contract's function several times under the different conditions the pause contract is expected to be in.

The following test checks that when the pause contract is not paused, the increment contract functions.

```rust
#![cfg(test)]
use crate::{Error, IncrementContract, IncrementContractArgs, IncrementContractClient, Pause};
use soroban_sdk::{contract, contractimpl, Env};

mod notpaused {
use super::*;
// highlight-start
#[contract]
pub struct Mock;
#[contractimpl]
impl Pause for Mock {
fn paused(_env: Env) -> bool {
false
}
}
// highlight-end
}

#[test]
fn test_notpaused() {
let env = Env::default();
// highlight-start
let pause_id = env.register(notpaused::Mock, ());
// highlight-end
let contract_id = env.register(
IncrementContract,
IncrementContractArgs::__constructor(&pause_id),
);
let client = IncrementContractClient::new(&env, &contract_id);

assert_eq!(client.increment(), 1);
assert_eq!(client.increment(), 2);
assert_eq!(client.increment(), 3);
}
```

The following test checks that when the pause contract is paused, the increment contract function rejects attempts to increment.
```rust
#![cfg(test)]
use crate::{Error, IncrementContract, IncrementContractArgs, IncrementContractClient, Pause};
use soroban_sdk::{contract, contractimpl, Env};

mod paused {
use super::*;
// highlight-start
#[contract]
pub struct Mock;
#[contractimpl]
impl Pause for Mock {
fn paused(_env: Env) -> bool {
true
}
}
// highlight-end
}

#[test]
fn test_paused() {
let env = Env::default();
// highlight-start
let pause_id = env.register(paused::Mock, ());
// highlight-end
let contract_id = env.register(
IncrementContract,
IncrementContractArgs::__constructor(&pause_id),
);
let client = IncrementContractClient::new(&env, &contract_id);

assert_eq!(client.try_increment(), Err(Ok(Error::Paused)));
}
```

Most tests, even integration tests, will look very similar to this unit test. They'll do four things:
1. Create an environment, the `Env`.
2. Register the contract(s) to be tested.
3. Invoke functions using the generated client.
4. Assert the outcome.

:::warning

Mocking introduces assumptions about the behavior of another contract. Even if the contract publishes an interface that says it'll return a bool (true/false), contracts can return any type.

```rust
impl Pause {
fn paused(env: Env) -> ? { ? }
}
```
This is one reason why it's helpful to test and fuzz using real dependencies and to code defensively assuming any external contract call could cause your contract to fail. See [Integration Tests] for how to test with real contracts.

The Rust Soroban SDK handles outgoing contract calls in a way that any unexpected error and unexpected type returned from the called contract to cause execution to stop. See [Making cross-contract calls] for more details.

:::

:::tip

The Rust Soroban SDK makes it just as easy to test against a real contract as it does to test against a mock of a contract, with little performance impact. In some ecosystems integration tests are avoided for practical reasons. Not in the Stellar ecosystem. Use mocks and integration tests to test thoroughly. See [Integration Tests].

:::

[increment-with-pause contract]: https://github.com/stellar/soroban-examples/blob/main/increment-with-pause/src/lib.rs
[Integration Tests]: ./integration-tests
[Making cross-contract calls]: ../conventions/cross-contract

0 comments on commit cad3b2a

Please sign in to comment.