<!-- Source: https://medius.k4tech.net/library/features/mock -->
# Mock

_Test without hardware_

A `MockBox` is an in-process fake Medius box behind the `mock` cargo feature.

```bash
cargo add medius --features mock
```

It's a cheap `Clone`: hand one to the [`Device`](/library/connection.md), keep one to script and inspect.

See also: [testing with MockBox](/library/guides/testing.md#testing).

## Building a MockBox

_new, and why you clone it_

```text
fn new() -> MockBox
```

_No round-trip_

`new()` records every command and auto-answers `QUERY(VERSION)` and `QUERY(HEALTH)` with defaults.

#### EXAMPLE

```rust
use medius::{Device, MockBox};

let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
// `device` drives the fake; `mock` still scripts and observes it.
```

## Wrapping it in a Device

_with_mock and open_mock_

```text
fn with_mock(mock: MockBox) -> Device
```

_No round-trip_

```text
fn open_mock(mock: MockBox) -> Result<Device>
```

_Blocks_

#### CONSTRUCTORS

| Constructor | Handshake | Returns | Description |
| --- | --- | --- | --- |
| `with_mock` | No | `Device` | Wraps the fake and hands back the device directly. |
| `open_mock` | Yes | [`Result<Device>`](/library/types/errors.md) | Also runs the version handshake, so it can fail the same way a real port can. |

#### EXAMPLE

```rust
use medius::{Device, MockBox};

let device = Device::open_mock(MockBox::new())?;
device.move_rel(5, 5)?;
```

See the [dead-box card](/library/features/mock.md#silent) for the two ways `open_mock` can fail.

## Scripting query answers

_Set the version, health, and device-info a query returns_

```text
fn with_version(self, version: Version) -> MockBox
```

_No round-trip_

```text
fn with_health(self, health: Health) -> MockBox
```

_No round-trip_

```text
fn with_device_info(self, device_info: DeviceInfo) -> MockBox
```

_No round-trip_

```text
fn with_caps(self, caps: Caps) -> MockBox
```

_No round-trip_

```text
fn with_mouse_caps(self, mouse: MouseCaps) -> MockBox
```

_No round-trip_

```text
fn with_kbd_caps(self, keyboard: KbdCaps) -> MockBox
```

_No round-trip_

```text
fn with_rate(self, rate: Rate) -> MockBox
```

_No round-trip_

```text
fn with_stats(self, stats: Stats) -> MockBox
```

_No round-trip_

```text
fn set_version(&self, version: Version)
```

_No round-trip_

```text
fn set_health(&self, health: Health)
```

_No round-trip_

The `with_*` builders set what each query returns: [`query_version`](/library/requests.md#version), [`query_health`](/library/requests.md#health), and the device-info queries ([`device_info`](/library/requests.md#device-info), [`caps`](/library/requests.md#caps), [`query_rate`](/library/requests.md#query-rate), [`query_stats`](/library/requests.md#query-stats)). `set_*` changes a live fake in place to flip the version or health mid-test. [`Version`](/library/types/structs.md#version), [`Health`](/library/types/structs.md#health), and the device-info [structs](/library/types/structs.md) live on the types page, and `Health::from_flags` builds one from the raw status byte.

#### EXAMPLE

```rust
use medius::{Device, Health, MockBox, Version};

let mock = MockBox::new()
    .with_version(Version { proto_ver: 3, fw_major: 5, fw_minor: 6, fw_patch: 7, mac: [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc], name: "Loki".into() })
    .with_health(Health::from_flags(0x0F));
let device = Device::with_mock(mock.clone());

let v = device.query_version()?;
assert_eq!((v.fw_major, v.fw_minor, v.fw_patch), (5, 6, 7));
assert!(device.query_health()?.mouse_attached);

// Change it mid-test: flip a later query_health.
mock.set_health(Health::from_flags(0x00));
assert!(!device.query_health()?.mouse_attached);
```

## Injecting inbound traffic

_push_log and push_raw_

```text
fn push_log(&self, level: LogLevel, text: &str)
```

_No round-trip_

```text
fn push_raw(&self, bytes: &[u8])
```

_No round-trip_

```text
fn push_motion(&self, seq: u8, dx: i16, dy: i16, dz: i16)
```

_No round-trip_

```text
fn push_usages(&self, seq: u8, usages: &[Usage])
```

_No round-trip_

All put bytes on the inbound stream as if the box emitted them. `push_log` frames a `LOG` line that surfaces on [`logs()`](/library/diagnostics.md#logs) as a [`LogLine`](/library/types/structs.md#log-line) ( [`LogLevel`](/library/types/enums.md#log-level) plus `text`); `push_raw` sends arbitrary bytes. The two event calls feed the [`EventStream`](/library/catch.md#event-stream): `push_motion` arrives as a [`CatchEvent::Motion`](/library/types/enums.md#catch-event) (a [`MotionEvent`](/library/types/structs.md#motion-event)) and `push_usages` as a [`CatchEvent::Usages`](/library/types/enums.md#catch-event) (a [`UsageSnapshot`](/library/types/structs.md#usage-snapshot)), with `seq` as the rolling counter so a test can assert gap detection.

#### EXAMPLE

```rust
use std::time::Duration;
use medius::{CatchEvent, CatchMask, Device, Key, LogLevel, MockBox, Usage};

let mock = MockBox::new();
let device = Device::with_mock(mock.clone());
let rx = device.logs();

mock.push_log(LogLevel::Warn, "overheating");
let line = rx.recv_timeout(Duration::from_secs(1))?;
assert_eq!(line.text, "overheating");

// Fake a catch subscription seeing the user hold A.
let stream = device.catch_events(CatchMask::KEYS)?;
mock.push_usages(0, &[Usage::from(Key::A)]);
assert!(matches!(stream.recv()?, CatchEvent::Usages(s) if s.is_held(Key::A)));
```

## Asserting what was sent

_recorded_frames, saw, recorded, clear_recorded_

```text
fn recorded_frames(&self) -> Vec<DecodedFrame>
```

_No round-trip_

```text
fn recorded(&self) -> usize
```

_No round-trip_

```text
fn saw(&self, ty: FrameType) -> bool
```

_No round-trip_

```text
fn clear_recorded(&self)
```

_No round-trip_

#### METHODS

| Method | Returns | Description |
| --- | --- | --- |
| `recorded_frames` | [`Vec<DecodedFrame>`](/library/types/frames.md) | Every command the host sent so far, decoded, in order. |
| `recorded` | `usize` | The count of commands recorded so far. |
| `saw` | `bool` | Whether the host sent at least one frame of the given type. |
| `clear_recorded` | `()` | Drops the recorded history so you can assert only on the next phase. |

A [`DecodedFrame`](/library/types/frames.md) is `{ ty, seq, payload }`; a [`press(Button::Left)`](/library/inject.md) records a [`FrameType::Inject`](/library/types/frames.md) frame with payload `[0, 0, 0, 1]` (class `0` = button, id `0`, action `1`).

#### EXAMPLE

```rust
use medius::{Button, Device, FrameType, MockBox};

let mock = MockBox::new();
let device = Device::with_mock(mock.clone());

device.press(Button::Left)?;

let frames = mock.recorded_frames();
let inject = frames
    .iter()
    .find(|f| f.ty == FrameType::Inject)
    .expect("press recorded");
assert_eq!(inject.payload, vec![0, 0, 0, 1]);
assert!(mock.saw(FrameType::Inject));

mock.clear_recorded(); // next assertions see a fresh log
```

## Simulating a dead box

_silent, and the handshake failures_

```text
fn silent(self) -> MockBox
```

_No round-trip_

`silent()` records commands but never answers a query. The two [`open_mock`](/library/features/mock.md#wrap) failures are a silent box ([`Error::NoReply`](/library/types/errors.md)) and an unknown protocol version ([`Error::BadProtoVer`](/library/types/errors.md)).

#### EXAMPLE

```rust
use medius::{Device, Error, MockBox, Version};

// A silent box answers nothing: no reply.
let err = Device::open_mock(MockBox::new().silent()).unwrap_err();
assert!(matches!(err, Error::NoReply));

// A box on an unknown protocol version fails the handshake.
let mock = MockBox::new().with_version(Version {
    proto_ver: 9,
    fw_major: 0,
    fw_minor: 0,
    fw_patch: 0,
    mac: [0; 6],
    name: String::new(),
});
let err = Device::open_mock(mock).unwrap_err();
assert!(matches!(err, Error::BadProtoVer { got: 9 }));
```
