> ## Documentation Index
> Fetch the complete documentation index at: https://nikita-shkoda.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing

> Run the search layer offline with the default test client or a stub for assertions.

Related: <a href="/projects/search-engine-for-typesense/v30/observability">Observability</a>,
<a href="/projects/search-engine-for-typesense/v30/dx">DX</a>,
<a href="/projects/search-engine-for-typesense/v30/client">Client</a>,
<a href="/projects/search-engine-for-typesense/v30/troubleshooting">Troubleshooting</a>

Test-only helpers to run the search layer offline (no I/O by default) and write
ergonomic assertions against unified events and compiled params.

## Quick start

The engine automatically uses an offline client in test mode (Rails test
environment or `RACK_ENV=test`). For programmable responses, enable the stub
client:

```ruby theme={null}
# test_helper.rb or spec_helper.rb
require 'search_engine/test'

SearchEngine.configure do |c|
  c.client = SearchEngine::Test::StubClient.new
end
```

* Queue responses and inspect captured calls:

```ruby theme={null}
stub = SearchEngine.client
stub.enqueue_response(:search, { 'hits' => [], 'found' => 0, 'out_of' => 0 })

# your relation execution here ...

calls = stub.search_calls
first = calls.first
first.url        # => compiled search URL
first.redacted?  # => true
first.redacted_body # => redacted subset for safe logs
```

* Programmable responses via blocks or exceptions:

```ruby theme={null}
stub.enqueue_response(:search, ->(req) { { 'hits' => [], 'found' => 42, 'out_of' => 42 } })
stub.enqueue_response(:multi_search, RuntimeError.new('boom'))
```

* Use the `SearchEngine.client` helper to access the configured client:

```ruby theme={null}
client = SearchEngine.client  # Returns StubClient, OfflineClient, or Client
```

## Event assertions

Unified events are emitted via ActiveSupport::Notifications. Capture them for a block and assert.

* RSpec matcher:

```ruby theme={null}
expect { rel.to_a }.to emit_event("search_engine.search").with(hash_including(collection: "books"))
```

* Minitest helpers:

```ruby theme={null}
include SearchEngine::Test::MinitestAssertions

assert_emits("search_engine.search", payload: ->(p) { p[:collection] == "books" }) { rel.to_a }

events = capture_events { rel.to_a }
```

* Params helpers in tests:

```ruby theme={null}
expect(rel.to_params_json).to include("filter_by")
```

See also: <code>Relation#to\_params\_json</code>, <code>Relation#to\_curl</code>, and <code>Relation#dry\_run!</code>.

## Test mode and offline client

The engine automatically enables test mode when running in Rails test environment
or when `RACK_ENV=test` is set. In test mode, `SearchEngine.client` returns a
no-op `SearchEngine::Test::OfflineClient` that avoids network I/O and returns
safe empty responses.

### Environment overrides

You can control test mode via environment variables:

* `SEARCH_ENGINE_TEST_MODE=1` or `SEARCH_ENGINE_TEST_MODE=0` (explicit override)
* `SEARCH_ENGINE_OFFLINE=1` or `SEARCH_ENGINE_OFFLINE=0` (legacy alias)

When `SearchEngine.config.client` is explicitly set (e.g., `StubClient`), it
always takes precedence over automatic offline mode.

### OfflineClient behavior

`SearchEngine::Test::OfflineClient` implements all client methods but performs
no network I/O:

* `search` returns empty results (`found: 0`)
* `multi_search` returns empty results array
* `import_documents` returns success JSONL
* All other operations return safe empty responses

## Offline strategy & safety

* Captured bodies and event payloads are redacted via the central helper.
* No API keys, raw filter literals, or PII are stored; long strings are truncated.
* A <code>redacted?</code> marker is present on captured request entries.

## Parallel test safety

* Internals are protected by a lightweight mutex.
* Use <code>stub.reset!</code> between examples if needed.

Backlinks: <a href="/projects/search-engine-for-typesense/v30/observability">Observability</a>, <a href="/projects/search-engine-for-typesense/v30/dx">DX</a>, <a href="/projects/search-engine-for-typesense/v30/cli">CLI</a>, <a href="/projects/search-engine-for-typesense/v30/client">Client</a>
