Open fueripe-desu opened 1 year ago
Your code example is quite large so I don't know where exactly the issue happens. Additionally, I'm not really familiar with your mock package. I guess there is some cleanup needed for the isolate we use internally for json decoding.
Your code example is quite large so I don't know where exactly the issue happens. Additionally, I'm not really familiar with your mock package. I guess there is some cleanup needed for the isolate we use internally for json decoding.
Yes, I shall admit that my code is a lot verbose, and this version I posted here had a little bug that I've later found out, because in the decorator used to generate mocks, this one:
@GenerateNiceMocks([
MockSpec<SupabaseClient>(),
MockSpec<SupabaseQueryBuilder>(),
MockSpec<PostgrestFilterBuilder<List<Map<String, dynamic>>>>(),
MockSpec<PostgrestResponse<List<Map<String, dynamic>>>>()
])
PostgrestFilterBuilder()
and PostgrestResponse()
were marked with the generic type List<Map<String, dynamic>>>
but Supabase returns List<dynamic>
instead, so I fixed it, but the bug still happens in the test.
Regarding the mockito package, it just implements a class getting rid of its required parameters and passing mock parameters to it, then you can simply mock the behavior of the class for example:
final mockQueryBuilder = MockSupabaseQueryBuilder();
final mockPostgrestFilterBuilder = MockPostgrestFilterBuilder();
when(mockQueryBuilder.select<List<Map<String, dynamic>>>(any))
.thenAnswer((_) => mockPostgrestFilterBuilder);
In this line, both these mock classes were created by mockito, so they don't really need parameters you can just instantiate them, then this when().thenAnswer()
method is just saying that when this method is called in the production code while running the test it will just return the value of thenAnswer()
just to mock the behavior because it's not the real class.
My guess of what could be the actual problem could be that mockito doesn't really work that well with isolates or the isolate used for JSON decoding in the actual Supabase source code has some kind of problem.
The workaround I used for solving this problem was creating a FakeSupabaseClient
class that copies the syntax of Supabase but it works totally offline without mocking the HTTP client.
Thanks for the explanation and simplification. I guess the constructor of SupabaseClient
is still called, which creates the isolate. Do you have any way to call .dispose()
on that object?
I tried calling the .dispose()
method like this to see if something would change:
test('Should return a list of languages from the remote database', () async {
// arrange
final fixtureMap = fixture('language_list_fixture.json');
final mockQueryBuilder = MockSupabaseQueryBuilder();
final parsedList = json.decode(fixtureMap) as List<dynamic>;
final expectedResult = parsedList
.map((dynamic item) =>
Map<String, dynamic>.from(item as Map<String, dynamic>))
.toList();
final mockPostgrestFilterBuilder = MockPostgrestFilterBuilder();
final mockPostgrestResponse = MockPostgrestResponse();
// Mock the behavior of `from` method
when(mockDatabaseClient.remote.from(any)).thenAnswer(
(_) => mockQueryBuilder,
);
// Mock the behavior of `select` method to return
// the PostgrestFilterBuilder instance
when(mockQueryBuilder.select<List<dynamic>>(any, any))
.thenAnswer((_) => mockPostgrestFilterBuilder);
// Return your expected result when using the mocked
// PostgrestFilterBuilder instance
when(mockPostgrestFilterBuilder.execute())
.thenAnswer((_) async => mockPostgrestResponse);
// Mock the behavior of `data` getter on PostgrestResponse to return
// your expected result
when(mockPostgrestResponse.data).thenReturn(expectedResult);
// act
final result = await syncRemoteDataSourceImpl.fetchLanguages();
// assert
expect(result, expectedResult);
await mockDatabaseClient.supabaseClient.dispose();
});
but mockito overrides the .dispose()
method, so it doesn't really have any effect, and I can't access the isolate because it is a private property of SupabaseClient.
@fueripe-desu We just published a package to mock Supabase client. It works a bit different from how mockito works, but would love to hear what you think. https://pub.dev/packages/mock_supabase_http_client
Describe the bug I'm trying to mock
SupabaseClient
using the mockito package and it keeps giving me this timeout exception:TimeoutException after 0:00:30.000000: Test timed out after 30 seconds. See https://pub.dev/packages/test#timeouts dart:isolate _RawReceivePort._handleMessage
To Reproduce In order to reproduce this exception, you can use the following test:
The definition of the
DatabaseClientInterface
is the following:The
fixture()
function just reads a string synchronously from the fixtures directory:String fixture(String name) => File('test/fixtures/$name').readAsStringSync();
The structure of the JSON file I'm trying to read (just for more context) is the following:
And here is the
SyncRemoteDatasourceImpl
definition:Expected behavior The expected behavior would be for the test to pass without throwing a timeout exception.
Version: On Linux/macOS
Additional context Since I'm very new to Supabase and everything, I don't really know if this is a bug or if it is my logic that is flawed, or if mocking the
SupabaseClient
is possible in the first place, but I have tried doing this in a lot of different ways and all of them just kept throwing me this timeout exception. Something I might want to mention is that, before doing this way, I started searching for more information about mocking theSupabaseClient
and I came across this old Github issue from thesupabase-dart
package, where the user was trying to achieve something very similar to what I want, and an example of a mock client was proposed in the issue, I used it and I had to adapt it and remove some errors, but then it kept throwing me this timeout exception.Here is the link of the issue I mentioned: https://github.com/supabase/supabase-dart/issues/12