Skip to main content

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');
});