name: write-test description: Write tests for the Stream Video Unity SDK. Use when creating, modifying, or reviewing test files. Covers assembly structure, naming conventions, base classes, async patterns, and all project-specific rules.
Write Test
Guidelines and conventions for writing tests in the Stream Video Unity SDK.
When to Use
- When creating a new test file or test method
- When modifying existing tests
- When reviewing test code for convention compliance
- When asked to add test coverage for a feature
Project Structure
Tests live under Tests/ in three assemblies:
| Assembly | Folder | Purpose | Inherits TestsBase? |
|---|---|---|---|
StreamVideo.Tests.Editor |
Tests/Editor/ |
Pure-logic editor tests — no networking or live client | No (unless the test needs a connection, e.g. StreamClientTests) |
StreamVideo.Tests.Runtime |
Tests/Runtime/ |
Integration tests — require a live Stream Video client | Yes, always |
StreamVideo.Tests.Shared |
Tests/Shared/ |
Shared base classes, utilities, test client infra | N/A (not tests themselves) |
Never create new .asmdef files. Place your file in the correct existing folder.
Compilation Guard
Every test .cs file must be wrapped in:
#if STREAM_TESTS_ENABLED
// ... entire file contents ...
#endif
This is a master kill-switch that prevents test code from compiling in production builds.
Namespaces
- Editor tests:
StreamVideo.Tests.Editor - Runtime tests:
StreamVideo.Tests.Runtime - Shared utilities:
StreamVideo.Tests.Shared
Class Conventions
- Mark test classes
internal(optionallysealedfor editor tests). - Name classes
{SubjectUnderTest}Tests— e.g.SessionIDTests,CallsTests. - Add an XML doc
<summary>comment on the class referencing the class under test via<see cref="..."/>. - Store the subject-under-test or any per-test state as private fields at the bottom of the class.
Test Method Naming
All tests (editor and runtime) use the same pattern: When_<condition>_expect_<outcome> with underscores:
When_new_instance_expect_empty
When_regenerate_expect_version_increments
When_clear_expect_version_unchanged
When_two_clients_join_same_call_expect_no_errors
When_setting_call_custom_data_expect_custom_data_set
When_participant_pinned_expect_pinned_participants_changed_event_fired
For runtime tests that use the async bridge, the private async companion uses the same name with an _Async suffix:
When_two_clients_join_same_call_expect_no_errors_Async
Writing an Editor Test (no client connection)
- Create
{Feature}Tests.csinTests/Editor/. - Do not inherit from
TestsBase. - Use
[Test]attribute for synchronous tests. - Use
[SetUp]for per-test initialization. - Use NUnit constraint-model assertions (
Assert.That(...)) with descriptive failure messages.
Template
#if STREAM_TESTS_ENABLED
using NUnit.Framework;
using StreamVideo.Core.SomeNamespace;
namespace StreamVideo.Tests.Editor
{
/// <summary>
/// Tests for <see cref="MyFeature"/>.
/// </summary>
internal sealed class MyFeatureTests
{
[SetUp]
public void SetUp()
{
_myFeature = new MyFeature();
}
[Test]
public void When_default_state_expect_some_property_has_expected_value()
{
Assert.That(_myFeature.SomeProperty, Is.EqualTo(expectedValue),
"Descriptive failure message explaining what went wrong.");
}
private MyFeature _myFeature;
}
}
#endif
Writing a Runtime Test (needs client connection)
- Create
{Feature}Tests.csinTests/Runtime/. - Inherit from
TestsBase. - Use
[UnityTest]attribute (returnsIEnumerator). - Delegate to
ConnectAndExecute(...)which handles client provisioning, connection, and retry-on-rate-limit. - Write the actual logic in a private
async Taskmethod with the_Asyncsuffix. - Choose the right
ConnectAndExecuteoverload:ConnectAndExecute(SingleClientTestHandler)— oneITestClientConnectAndExecute(TwoClientsTestHandler)— twoITestClientparamsConnectAndExecute(Func<Task>)— no client params (connection still happens)
Template
#if STREAM_TESTS_ENABLED
using System.Collections;
using System.Threading.Tasks;
using NUnit.Framework;
using StreamVideo.Tests.Shared;
using UnityEngine.TestTools;
namespace StreamVideo.Tests.Runtime
{
/// <summary>
/// Tests for <see cref="SomeFeature"/>.
/// </summary>
internal class SomeFeatureTests : TestsBase
{
[UnityTest]
public IEnumerator When_doing_X_expect_Y()
=> ConnectAndExecute(When_doing_X_expect_Y_Async);
private async Task When_doing_X_expect_Y_Async(ITestClient client)
{
var call = await client.JoinRandomCallAsync();
// ... test logic ...
await WaitForConditionAsync(() => /* condition to poll */);
Assert.AreEqual(expected, actual);
}
}
}
#endif
The Async-to-Coroutine Bridge
Unity's [UnityTest] requires IEnumerator, but test logic is async Task. The bridge works like this:
[UnityTest] public IEnumerator MethodName()
=> ConnectAndExecute(MethodName_Async);
ConnectAndExecute internally calls RunAsIEnumerator() (from TestUtils) which polls task.IsCompleted each frame and rethrows faults. You never need to call RunAsIEnumerator() directly in a runtime test that uses ConnectAndExecute.
For editor tests that are async but don't need a client, use the Execute(...) helper:
[UnityTest]
public IEnumerator My_async_editor_test()
=> Execute(My_async_editor_test_Async);
Or call RunAsIEnumerator() directly on the task:
[UnityTest]
public IEnumerator My_async_editor_test()
=> My_async_editor_test_Async().RunAsIEnumerator();
Key Shared Utilities
| Utility | What it does |
|---|---|
TestsBase |
Base class for runtime tests. Manages client lifecycle, teardown, retry logic. |
ITestClient / TestClient |
Wraps IStreamVideoClient with test helpers like JoinRandomCallAsync(). |
StreamTestClientProvider |
Singleton that pools and reuses IStreamVideoClient instances across fixtures. |
WaitForConditionAsync(condition, timeoutMs) |
Polls a Func<bool> until true or timeout. Use instead of Task.Delay. |
DisposableAssetsProvider |
Tracks and disposes Unity assets (e.g. WebCamTexture) created during tests. |
TestUtils.RunAsIEnumerator() |
Extension method bridging Task → IEnumerator for Unity test runner. |
TestUtils.TryGetFirstWorkingCameraDeviceAsync() |
Finds a working camera device for video tests. |
Conditional Ignore for Camera Tests
If a test requires a camera device, add the conditional ignore attribute:
[UnityTest, ConditionalIgnore(IgnoreConditionNoCameraKey, IgnoreConditionNoCameraReason)]
public IEnumerator When_client_joins_call_with_video_expect_receiving_video_track()
=> ConnectAndExecute(When_client_joins_call_with_video_expect_receiving_video_track_Async,
ignoreFailingMessages: true);
These constants are defined in TestsBase.
Golden Rules
- Always wrap files in
#if STREAM_TESTS_ENABLED/#endif. - Never create a new
.asmdef— use the existingTests/Editor/orTests/Runtime/folder. - Inherit
TestsBasefor runtime tests; don't for pure-logic editor tests. - Follow the naming pattern:
When_<condition>_expect_<outcome>for all tests (editor and runtime). - Use the async-bridge pattern: public
[UnityTest]→IEnumerator→ConnectAndExecute→ privateasync Taskwith_Asyncsuffix. - Add descriptive assertion messages to every assertion.
- Don't manage client lifecycle manually — let
TestsBaseandStreamTestClientProviderhandle it. - Use
WaitForConditionAsyncinstead ofTask.Delayfor polling asynchronous state. - Track disposable assets via
DisposableAssetsProvider. - Keep test classes
internal— tests are not public API. - Add
<summary>XML doc comments on the class with<see cref="..."/>to the subject under test. - Place shared helpers in
Tests/Shared/— never duplicate utilities across Editor and Runtime.