JSON Response Testing
The server_testing package provides powerful JSON assertion capabilities via TestResponse.assertJson and AssertableJson (inspired by Laravel's testing API). The fluent matcher DSL comes from the assertable_json package, which server_testing re-exports—importing package:server_testing/server_testing.dart is all you need.
Basic Assertions
test('test json response', () async {
final response = await client.getJson('/api/data');
response.assertJson((json) {
json
.has('id') // Key exists
.has('user.name') // Nested key exists
.missing('deleted_at') // Key doesn't exist
.where('status', 'active') // Exact value match
.whereNot('type', 'admin') // Value doesn't match
.whereType<int>('id') // Type check
.whereIn('role', ['user', 'admin']); // Value in list
});
});
Nested Data Testing
response.assertJson((json) {
// Test nested object
json.scope('user', (user) {
user
.where('id', 1)
.where('name', 'John')
.scope('profile', (profile) {
profile
.has('avatar')
.has('bio');
});
});
});
Array Testing
response.assertJson((json) {
// Count array items
json.count('items', 3);
// Count between range
json.countBetween('items', 1, 5);
// Test first item
json.scope('items', (items) {
items.first((item) {
item.where('id', 1);
});
});
// Test each item
json.scope('items', (items) {
items.each((item) {
item
.has('id')
.has('name')
.whereType<int>('id');
});
});
});
Numeric Testing
response.assertJson((json) {
json
.isGreaterThan('age', 18)
.isLessThan('price', 100.0)
.isGreaterOrEqual('quantity', 1)
.isLessOrEqual('discount', 50)
.isBetween('score', 0, 100)
.isDivisibleBy('quantity', 5)
.isMultipleOf('price', 10)
.isPositive('balance')
.isNegative('debt');
});
Schema Validation
response.assertJson((json) {
// Simple schema
json.matchesSchema({
'id': int,
'name': String,
'active': bool
});
// Complex schema with arrays
json.assertStructure({
'users': {
'*': [ // Wildcard for array items
'id',
'name',
'email'
]
},
'meta': {
'total',
'per_page'
}
});
});
Conditional Testing
response.assertJson((json) {
// Only test if condition is true
json.when(isAdmin, (json) {
json.has('admin_features');
});
// Only test if condition is false
json.unless(isBasicUser, (json) {
json.has('premium_features');
});
});
Fragment Testing
Property Interaction Tracking
The assertJson method tracks which properties have been checked in your assertions using the etc() method:
response.assertJson((json) {
json
.where('id', 1)
.where('name', 'John')
// Mark all remaining properties as checked
.etc();
});
// Without etc(), this would fail if response contains other properties
response.assertJson((json) {
json
.where('id', 1)
// Will fail if response has any other properties
// besides 'id' because they weren't checked
});
// Use etc() in nested scopes
response.assertJson((json) {
json.scope('user', (user) {
user
.where('id', 1)
.where('name', 'John')
.etc(); // Mark all user properties as checked
})
.etc(); // Mark all root properties as checked
});
This helps ensure you're testing all properties in your responses and not accidentally ignoring unexpected data.
// Test part of response matches
response.assertJson((json) {
json.assertFragment({
'user': {
'id': 1,
'name': 'John'
}
});
});
Debugging
response
// Print response for debugging
.dump()
// Tap into assertion chain
.tap((json) {
print('Testing JSON: ${json.toString()}');
})
// Continue assertions
.assertJson((json) {
json.has('data');
});