Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
4beca46
test(llc): add stream_chat_test package with BDD-style test helpers
VelikovPetar Sep 9, 2026
16f16d0
test(llc): polish stream_chat_test docs and expose isLocalUnreadCount…
VelikovPetar Sep 9, 2026
8aa31ea
Merge branch 'master' into feat/stream-chat-test-package
VelikovPetar Sep 9, 2026
4bb5540
test(llc): wire stream_chat_test as a dev dependency of stream_chat
VelikovPetar Sep 10, 2026
b049eb1
test(llc): add matchers, error fixtures and persistence doubles to st…
VelikovPetar Sep 10, 2026
8ad28a3
test(llc): migrate typing event tests to stream_chat_test
VelikovPetar Sep 10, 2026
1ec4aed
test(llc): serialize emitted events with server fidelity in stream_ch…
VelikovPetar Sep 10, 2026
1b2fc78
test(llc): migrate message.new event tests to stream_chat_test
VelikovPetar Sep 10, 2026
97aa8a2
test(llc): migrate message.updated event tests to stream_chat_test
VelikovPetar Sep 10, 2026
f855d9e
test(llc): migrate message.deleted event tests to stream_chat_test
VelikovPetar Sep 10, 2026
2908ed8
test(llc): migrate channel truncated event tests to stream_chat_test
VelikovPetar Sep 10, 2026
6b0f2ef
test(llc): migrate channel updated event tests to stream_chat_test
VelikovPetar Sep 10, 2026
aa7c64f
test(llc): migrate reaction event tests to stream_chat_test
VelikovPetar Sep 10, 2026
f589f94
test(llc): migrate shown thread reply event tests to stream_chat_test
VelikovPetar Sep 10, 2026
ece2670
test(llc): migrate watching event tests to stream_chat_test
VelikovPetar Sep 10, 2026
02f84fc
test(llc): migrate member event tests to stream_chat_test
VelikovPetar Sep 10, 2026
ff4f308
test(llc): migrate poll event tests to stream_chat_test
VelikovPetar Sep 10, 2026
b9cf801
test(llc): migrate reminder event tests to stream_chat_test
VelikovPetar Sep 10, 2026
842b1a6
test(llc): migrate draft event tests to stream_chat_test
VelikovPetar Sep 10, 2026
0204390
test(llc): migrate read event tests to stream_chat_test
VelikovPetar Sep 10, 2026
4ea1c06
test(llc): migrate channel push preference event tests to stream_chat…
VelikovPetar Sep 10, 2026
59b11a8
test(llc): migrate location event tests to stream_chat_test
VelikovPetar Sep 10, 2026
05592d8
test(llc): migrate user messages deleted event tests to stream_chat_test
VelikovPetar Sep 10, 2026
b4c1356
test(llc): migrate message enrichment merge tests to stream_chat_test
VelikovPetar Sep 10, 2026
b265bb8
test(llc): migrate updateMessage quoted-rewrite tests to stream_chat_…
VelikovPetar Sep 10, 2026
81a5022
test(llc): migrate updateChannelState identity guard tests to stream_…
VelikovPetar Sep 10, 2026
edb2639
test(llc): migrate local unread count tests and retire channel_client…
VelikovPetar Sep 10, 2026
0276138
test(llc): migrate channel search and sendAction tests to stream_chat…
VelikovPetar Sep 10, 2026
941b7b8
test(llc): migrate channel pin message tests to stream_chat_test
VelikovPetar Sep 10, 2026
8645a2f
test(llc): migrate channel reminder tests to stream_chat_test
VelikovPetar Sep 10, 2026
f39ebcb
test(llc): migrate channel draft tests to stream_chat_test
VelikovPetar Sep 10, 2026
d75280b
test(llc): migrate channel misc operation tests to stream_chat_test
VelikovPetar Sep 10, 2026
b002636
test(llc): migrate channel delete message tests to stream_chat_test
VelikovPetar Sep 10, 2026
ef3a1f8
test(llc): migrate channel update message tests to stream_chat_test
VelikovPetar Sep 10, 2026
b2caaf7
test(llc): migrate channel location API tests to stream_chat_test
VelikovPetar Sep 10, 2026
96dd503
test(llc): migrate channel send message tests to stream_chat_test
VelikovPetar Sep 10, 2026
3b22c86
test(llc): migrate channel watch and message query tests to stream_ch…
VelikovPetar Sep 10, 2026
b71c595
test(llc): migrate channel update and member API tests to stream_chat…
VelikovPetar Sep 10, 2026
3802b8c
test(llc): migrate channel reaction tests to stream_chat_test
VelikovPetar Sep 10, 2026
69043c8
test(llc): migrate channel state cleanup tests to stream_chat_test
VelikovPetar Sep 10, 2026
8d32d87
test(llc): migrate channel moderation and display API tests to stream…
VelikovPetar Sep 10, 2026
5124e28
test(llc): migrate channel query tests to stream_chat_test
VelikovPetar Sep 10, 2026
2490ee3
test(llc): migrate channel read receipt tests to stream_chat_test
VelikovPetar Sep 10, 2026
07d605f
test(llc): migrate channel typing indicator tests to stream_chat_test
VelikovPetar Sep 10, 2026
4969d16
test(llc): migrate channel filter tags tests to stream_chat_test
VelikovPetar Sep 10, 2026
a81fc62
test(llc): migrate channel state validation and count event tests to …
VelikovPetar Sep 10, 2026
823e146
test(llc): migrate channel setter tests to stream_chat_test
VelikovPetar Sep 10, 2026
c90dc66
test(llc): migrate channel persistence lifecycle test to stream_chat_…
VelikovPetar Sep 10, 2026
1011b7e
test(llc): migrate retry tests and retire channel_test monolith
VelikovPetar Sep 10, 2026
84906ce
test(llc): migrate channel read helper tests to stream_chat_test
VelikovPetar Sep 10, 2026
d5c5936
test(llc): migrate channel capability check tests to stream_chat_test
VelikovPetar Sep 10, 2026
cf35999
test(llc): make satellite tests independent of shared test helpers
VelikovPetar Sep 10, 2026
5d7950e
test(llc): migrate client state guard tests to stream_chat_test
VelikovPetar Sep 10, 2026
8080a2f
test(llc): migrate client WS event tests to stream_chat_test
VelikovPetar Sep 10, 2026
4a07355
test(llc): carve out reconnection tests into a self-contained legacy …
VelikovPetar Sep 10, 2026
508735a
test(llc): migrate client poll API tests to stream_chat_test
VelikovPetar Sep 10, 2026
237645d
test(llc): migrate client channel API tests to stream_chat_test
VelikovPetar Sep 10, 2026
55e8c08
test(llc): migrate client API delegation tests to stream_chat_test
VelikovPetar Sep 10, 2026
ddf440f
test(llc): migrate client queryChannels tests to stream_chat_test
VelikovPetar Sep 10, 2026
1ca84d2
test(llc): migrate client sync tests to stream_chat_test
VelikovPetar Sep 10, 2026
1e1b1e7
test(llc): migrate client live location tests to stream_chat_test
VelikovPetar Sep 10, 2026
adf559e
test(llc): migrate client user API and block user tests to stream_cha…
VelikovPetar Sep 10, 2026
1789b9e
test(llc): migrate client with-persistence tests to stream_chat_test
VelikovPetar Sep 10, 2026
e4efeac
test(llc): add migrated client connection, pin message and persistenc…
VelikovPetar Sep 11, 2026
15c4450
test(llc): carve out anonymous persistence connect test into client_r…
VelikovPetar Sep 11, 2026
31271bc
test(llc): remove migrated persistence connection tests from client_test
VelikovPetar Sep 11, 2026
48a99e4
test(llc): migrate client unpinMessage, enrichUrl and currentUser unr…
VelikovPetar Sep 11, 2026
43b3381
test(llc): remove migrated pin message and trailing tests from client…
VelikovPetar Sep 11, 2026
ff2604f
test(llc): remove migrated client API delegation tests from client_test
VelikovPetar Sep 11, 2026
6474b74
test(llc): remove migrated connection tests from client_test
VelikovPetar Sep 11, 2026
69caf38
test(llc): retire client_test monolith
VelikovPetar Sep 11, 2026
22e9836
test(llc): retire test/src/matchers.dart in favour of the stream_chat…
VelikovPetar Sep 11, 2026
13b12cb
test(llc): retire test/src/fakes.dart by inlining its two surviving d…
VelikovPetar Sep 11, 2026
d048805
test(llc): shrink test/src/mocks.dart to the doubles below the algebr…
VelikovPetar Sep 11, 2026
6674928
docs(test): record the migration-era stream_chat_test helper additions
VelikovPetar Sep 11, 2026
21f8c9c
test(llc): build retry queue test doubles per test so assertions stop…
VelikovPetar Sep 11, 2026
993f657
test(llc): scope the local mocks import so it cannot clash with the s…
VelikovPetar Sep 11, 2026
eb7f0d7
test(stream_chat_test): pin the server-fidelity fields each serverXxx…
VelikovPetar Sep 11, 2026
9758334
test(llc): cover the channel config getter on initialized and non-ini…
VelikovPetar Sep 11, 2026
60c0cb6
test(llc): restore the event payload pinning the old channel on and s…
VelikovPetar Sep 11, 2026
92e71d3
test(llc): assert the connected user against the harness user, not ag…
VelikovPetar Sep 11, 2026
51bd3ed
docs(llc): point the connection test carve-out note at client_reconne…
VelikovPetar Sep 11, 2026
546a0ff
test(llc): cover the retry queue giving up when the policy declines t…
VelikovPetar Sep 11, 2026
e1d10df
test(stream_chat_test): let skip carry a reason now that the feeds pa…
VelikovPetar Sep 11, 2026
ee46a2c
fix(stream_chat_test): restore moderation, mentioned groups and chann…
VelikovPetar Sep 11, 2026
ca96e3d
docs(stream_chat_test): correct the guarded-zone and fallback-registr…
VelikovPetar Sep 11, 2026
5582631
fix(stream_chat_test): give the default channel model a deterministic…
VelikovPetar Sep 11, 2026
e4a8799
docs(stream_chat_test): match the repo changelog format and cover the…
VelikovPetar Sep 11, 2026
dd924f4
test(llc): assert no delivery is reported for a channel without a cid
VelikovPetar Sep 11, 2026
4059d54
fix(stream_chat_test): echo the configured user as the connected user…
VelikovPetar Sep 11, 2026
bd5b663
test(llc): pin the parent id on typing events and the exact event on …
VelikovPetar Sep 11, 2026
a3baf96
docs(stream_chat_test): describe verify and tearDown as a convention,…
VelikovPetar Sep 11, 2026
d73c7d3
test(llc): await the sync guard tests so they can actually detect an …
VelikovPetar Sep 11, 2026
011a03d
test(llc): cover the auth interceptor passing through a non token-exp…
VelikovPetar Sep 11, 2026
89fefdf
test(llc): pin the client's default REST stack wiring
VelikovPetar Sep 11, 2026
14b8c44
test(stream_chat_test): stop tagging every harness test by default
VelikovPetar Sep 11, 2026
fe62472
docs(llc): make test comments stand on their own instead of pointing …
VelikovPetar Sep 11, 2026
32fda91
docs(repo): document the stream_chat_test harness in the style and te…
VelikovPetar Sep 11, 2026
9e915c5
Merge branch 'master' into feat/stream-chat-test-package
VelikovPetar Sep 11, 2026
3fbdf13
Merge branch 'feat/stream-chat-test-package' into test/migrate-llc-te…
VelikovPetar Sep 11, 2026
272953c
Merge branch 'master' into feat/stream-chat-test-package
VelikovPetar Sep 16, 2026
aac8e50
Merge branch 'feat/stream-chat-test-package' into test/migrate-llc-te…
VelikovPetar Sep 16, 2026
bb4c94f
test(llc): assert the non-member user merge does not write channel state
VelikovPetar Sep 16, 2026
3ec03a7
test(llc): reassemble the migrated suite into the original test files
VelikovPetar Sep 16, 2026
ca3873f
Merge branch 'master' into feat/stream-chat-test-package
VelikovPetar Sep 16, 2026
01987ce
Merge remote-tracking branch 'origin/feat/stream-chat-test-package' i…
VelikovPetar Sep 16, 2026
266adf0
test(llc): retire test/src/fakes.dart in favour of the shared harness…
VelikovPetar Sep 17, 2026
30dd3ad
test(llc): build the delivery reporter's channels on the shared harne…
VelikovPetar Sep 17, 2026
4d1c9c4
test(llc): scope the shared mock imports that shadow the harness doubles
VelikovPetar Sep 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions STYLE_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,10 @@ functions called inside each test block. For cleanup, prefer `addTearDown` over
global `tearDown` callback — `addTearDown` registers cleanup at the exact point a
resource is created, ensuring it only runs if initialization succeeded.

The `setUp:` parameter of `chatClientTest` / `channelTest` is a different thing and is
fine to use: it runs once per test against that test's own tester, and shares nothing
with the tests around it.

### Prefer more test files, avoid long test files

Organize tests into smaller files grouped by feature, widget, or behavior. It's easier
Expand Down Expand Up @@ -1029,6 +1033,52 @@ persistence client, WebSocket). Do not mock every collaborator.
mock.method(any(named: 'foo')))` pattern; do not hand-roll a fake that duplicates the
full interface.

For `stream_chat` those seams are already replaced for you — see
[Use the `stream_chat_test` harness for `stream_chat` tests](#use-the-stream_chat_test-harness-for-stream_chat-tests).

### Use the `stream_chat_test` harness for `stream_chat` tests

`chatClientTest` / `channelTest`, from the `stream_chat_test` package, build a **real**
`StreamChatClient` with only the REST API, the WebSocket transport and — when asked
for — the persistence client swapped out, so event decoding, state, retries and
reconnection all run production code. Do not hand-roll a mocked `StreamChatClient`
for these tests. See the package's README for the full API.

```dart
// BAD — mocking the subject itself; nothing underneath it is exercised.
final client = MockStreamChatClient();
when(() => client.markAllRead()).thenAnswer((_) async => EmptyResponse());

// GOOD — the client, its state and the event pipeline are real; only the
// REST call is stubbed.
chatClientTest(
'`.markAllRead` marks every channel as read',
body: (tester) async {
tester.mockApi(
(api) => api.channel.markAllRead(),
result: createDefaultEmptyResponse(),
);

await tester.client.markAllRead();

tester.verifyApi((api) => api.channel.markAllRead());
},
);
```

Three conventions keep that harness trustworthy. Breaking them tends to produce a
confusing mismatch rather than a clear failure:

- **Stub with exact argument values wherever the value is known.** A mocktail stub
only answers on a match, so an exact stub doubles as verification of the request
the SDK actually sent. Reach for `any()` only when the SDK stamps the argument
itself (e.g. a `Message`, which gets a local timestamp and sender attached).
- **Keep fixtures deterministic.** Build them with the `createDefaultXxx` factories
and pass timestamps explicitly — a `DateTime.now()` in test data makes
equality-based matching differ from run to run.
- **Use UTC for any `DateTime` inside an emitted event.** Emitted events go through
real JSON decoding, and local times do not survive that round-trip intact.

### Golden tests

Widget tests that verify pixel-level rendering use the `alchemist` package. Golden
Expand Down
6 changes: 6 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ The helpers (`_emptyChannelListController`, `_pumpChannelListView`) live at the
of the test file. Their names tell the reader what they do; the reader doesn't need to
look inside unless something breaks.

When testing `stream_chat`, the `connect:` and `setUp:` phases of `chatClientTest` /
`channelTest` are where this setup belongs, so the body holds only the behavior under
test.

## Optimize tests for comprehension

Even a well-factored test benefits from small edits that separate "the thing under
Expand Down Expand Up @@ -241,5 +245,7 @@ your machine — see [Golden tests in STYLE_GUIDE.md](STYLE_GUIDE.md#golden-test

- [STYLE_GUIDE.md — Testing](STYLE_GUIDE.md#testing) — repo-level testing conventions
(mocktail, alchemist golden tests, self-contained tests, `addTearDown`).
- [`packages/stream_chat_test/README.md`](packages/stream_chat_test/README.md) — the
`chatClientTest` / `channelTest` harness used by the `stream_chat` test suite.
- Flutter's [Writing-Effective-Tests](https://github.com/flutter/flutter/blob/master/docs/contributing/testing/Writing-Effective-Tests.md)
— the source this guide was adapted from.
2 changes: 2 additions & 0 deletions melos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ command:
media_kit_video: ^2.0.1
meta: ^1.9.1
mime: ^2.0.0
mocktail: ^1.0.5
package_info_plus: ">=9.0.1 <11.0.0"
path: ^1.9.1
path_provider: ^2.1.5
Expand Down Expand Up @@ -116,6 +117,7 @@ command:
ref: 2aea1413997af886ee951921d71e6b8cb8c5cfbb
path: packages/stream_thumbnail
synchronized: ^3.4.0
test: ^1.31.1
url_launcher: ^6.3.2
uuid: ^4.5.3
video_player: ^2.11.1
Expand Down
2 changes: 2 additions & 0 deletions packages/stream_chat/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,6 @@ dev_dependencies:
freezed: ^3.0.0
json_serializable: ^6.13.2
mocktail: ^1.0.5
stream_chat_test:
path: ../stream_chat_test
test: ^1.31.1
Original file line number Diff line number Diff line change
@@ -1,60 +1,35 @@
// ignore_for_file: deprecated_member_use_from_same_package

import 'package:mocktail/mocktail.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:test/test.dart';

import '../../fakes.dart';
import '../../mocks.dart';
import 'package:stream_chat_test/stream_chat_test.dart';

void main() {
group('ChannelCapabilityCheck', () {
const channelId = 'test-channel-id';
const channelType = 'test-channel-type';
late final client = MockStreamChatClient();

setUpAll(() {
// detached loggers
when(() => client.detachedLogger(any())).thenAnswer((invocation) {
final name = invocation.positionalArguments.first;
return _createLogger(name);
});

final retryPolicy = RetryPolicy(
shouldRetry: (_, __, ___) => false,
delayFactor: Duration.zero,
);
when(() => client.retryPolicy).thenReturn(retryPolicy);

// fake clientState
final clientState = FakeClientState();
when(() => client.state).thenReturn(clientState);

// client logger
when(() => client.logger).thenReturn(_createLogger('mock-client-logger'));
});

/// Parameterized test for channel capability extension properties
/// Parameterized test for channel capability extension properties.
void testCapability(
String capabilityName,
ChannelCapability capability,
bool Function(Channel) getterMethod,
) {
test('can$capabilityName returns false when capability is absent', () {
final channelState = _generateChannelState(channelId, channelType);
final channel = Channel.fromState(client, channelState);
expect(getterMethod(channel), false);
});

test('can$capabilityName returns true when capability is present', () {
final channelState = _generateChannelState(
channelId,
channelType,
ownCapabilities: [capability],
);
final channel = Channel.fromState(client, channelState);
expect(getterMethod(channel), true);
});
channelTest(
'can$capabilityName returns false when capability is absent',
setUp: (tester) => tester.watch(),
body: (tester) async {
expect(getterMethod(tester.channel), false);
},
);

channelTest(
'can$capabilityName returns true when capability is present',
setUp: (tester) => tester.watch(
modifyResponse: (state) => state.copyWith(
channel: createDefaultChannelModel(ownCapabilities: [capability]),
),
),
body: (tester) async {
expect(getterMethod(tester.channel), true);
},
);
}

// Test all channel capabilities using the parameterized function
Expand Down Expand Up @@ -310,101 +285,73 @@ void main() {
(channel) => channel.canNotifyGroup,
);

test('returns correct values with multiple capabilities', () {
final channelState = _generateChannelState(
channelId,
channelType,
ownCapabilities: [
ChannelCapability.sendMessage,
ChannelCapability.sendReply,
ChannelCapability.deleteOwnMessage,
],
channelTest(
'returns correct values with multiple capabilities',
setUp: (tester) => tester.watch(
modifyResponse: (state) => state.copyWith(
channel: createDefaultChannelModel(
ownCapabilities: [
ChannelCapability.sendMessage,
ChannelCapability.sendReply,
ChannelCapability.deleteOwnMessage,
],
),
),
),
body: (tester) async {
expect(tester.channel.canSendMessage, true);
expect(tester.channel.canSendReply, true);
expect(tester.channel.canDeleteOwnMessage, true);
expect(tester.channel.canDeleteAnyMessage, false);
expect(tester.channel.canUpdateChannel, false);
},
);
group('usesLocalUnreadCount', () {
channelTest(
'is false when disabled and read receipts are unavailable',
setUp: (tester) => tester.watch(),
body: (tester) async {
expect(tester.channel.usesLocalUnreadCount, false);
},
);

channelTest(
'is false when disabled and read receipts are available',
setUp: (tester) => tester.watch(
modifyResponse: (state) => state.copyWith(
channel: createDefaultChannelModel(
ownCapabilities: [ChannelCapability.readEvents],
),
),
),
body: (tester) async {
expect(tester.channel.usesLocalUnreadCount, false);
},
);

final channel = Channel.fromState(client, channelState);
expect(channel.canSendMessage, true);
expect(channel.canSendReply, true);
expect(channel.canDeleteOwnMessage, true);
expect(channel.canDeleteAnyMessage, false);
expect(channel.canUpdateChannel, false);
});
channelTest(
'is false when enabled but the channel supports read receipts',
isLocalUnreadCountEnabled: true,
setUp: (tester) => tester.watch(
modifyResponse: (state) => state.copyWith(
channel: createDefaultChannelModel(
ownCapabilities: [ChannelCapability.readEvents],
),
),
),
body: (tester) async {
expect(tester.channel.usesLocalUnreadCount, false);
},
);

group('usesLocalUnreadCount', () {
// `isLocalUnreadCountEnabled` is a settable field on the mock and the
// client is shared across the group, so reset it between tests.
tearDown(() => client.isLocalUnreadCountEnabled = false);

Channel channelWithReadEvents({required bool available}) {
final channelState = _generateChannelState(
channelId,
channelType,
ownCapabilities: [
if (available) ChannelCapability.readEvents,
],
);

final channel = Channel.fromState(client, channelState);
addTearDown(channel.dispose);

return channel;
}

test('is false when disabled and read receipts are unavailable', () {
client.isLocalUnreadCountEnabled = false;
final channel = channelWithReadEvents(available: false);
expect(channel.usesLocalUnreadCount, false);
});

test('is false when disabled and read receipts are available', () {
client.isLocalUnreadCountEnabled = false;
final channel = channelWithReadEvents(available: true);
expect(channel.usesLocalUnreadCount, false);
});

test('is false when enabled but the channel supports read receipts', () {
client.isLocalUnreadCountEnabled = true;
final channel = channelWithReadEvents(available: true);
expect(channel.usesLocalUnreadCount, false);
});

test('is true when enabled and read receipts are unavailable', () {
client.isLocalUnreadCountEnabled = true;
final channel = channelWithReadEvents(available: false);
expect(channel.usesLocalUnreadCount, true);
});
channelTest(
'is true when enabled and read receipts are unavailable',
isLocalUnreadCountEnabled: true,
setUp: (tester) => tester.watch(),
body: (tester) async {
expect(tester.channel.usesLocalUnreadCount, true);
},
);
});
});
}

// region Test Helpers

ChannelState _generateChannelState(
String channelId,
String channelType, {
DateTime? lastMessageAt,
List<ChannelCapability>? ownCapabilities,
bool mockChannelConfig = false,
}) {
ChannelConfig? config;
if (mockChannelConfig) {
config = MockChannelConfig();
when(() => config!.readEvents).thenReturn(true);
when(() => config!.typingEvents).thenReturn(true);
}
final channel = ChannelModel(
id: channelId,
type: channelType,
config: config,
ownCapabilities: ownCapabilities,
lastMessageAt: lastMessageAt,
);
return ChannelState(channel: channel);
}

Logger _createLogger(String name) {
final logger = Logger.detached(name)..level = Level.ALL;
logger.onRecord.listen(print);
return logger;
}

// endregion
Loading
Loading