Why tests passes individually and fail on terminal CLI execution? - flutter

I'm writting tests to ensure a set of initialization rules on an object with an optional parameter, like the one below:
part 'example.g.dart';
#JsonSerializable(createToJson: false)
class Example {
String req1;
String req2;
String? opt;
Example({required this.req1, required this.req2, this.opt});
factory Example.fromJson(Map<String, dynamic> json) =>
_$ExampleFromJson(json);
}
My .g.dart is the following:
Example _$ExampleFromJson(Map<String, dynamic> json) {
return Example(
req1: json['req1'] as String,
req2: json['req2'] as String,
opt: json['opt'] as String?,
);
}
The following two tests passes when executed individually, through android studio GUI, and fail through in flutter test CLI execution.
test('should fail when json without req1 is provided', () {
var testJson = {
'req2': 'a-req2',
};
expect(() => Example.fromJson(testJson), throwsA(isA<TypeError>()));
});
test('should fail when json without req2 is provided', () {
var testJson = {
'req1': 'a-req1',
};
expect(() => Example.fromJson(testJson), throwsA(isA<TypeError>()));
});
Dart version from android studio and terminal are different (2.10 and 2.17.6), but the execution also fails in our CI, which has the same version of GUI, which works.
The error presented when the pipeline executes is the following:
Expected: throws <Instance of 'TypeError'>
Actual: <Closure: () => Example>
Which: returned <Instance of 'Example'>
So the object does get instantiated, even with null value in required fields.
Any idea why?

Related

How to test Function passed as an argument in Flutter?

How to test Function passed as an argument in Flutter?
code:
Future<User> execute({
required String username,
required String password,
required void Function(AuthFailure fail) onFailure,
required void Function(User user) onSuccess,
}) async {
if (username.isNonValid || password.isNonValid) {
onFailure(const AuthFailure.wrongCredentials()); // I want to test this line
return const User.anonymous();
}
...
}
test:
test('use case - failure execution for incorrect credentials', () async {
// GIVEN
// WHEN
final user = await useCase.execute(
username: "noname",
password: "password",
onFailure: (fail) {},
onSuccess: (user) {},
);
// THEN
// TODO how to verify onFailure call inside useCase?
expect(user, const User.anonymous());
});
Or maybe testing this way is not the idiomatic way, because the test becomes more white-box instead black-box? Should I perceive passing functions as arguments to use cases as anti-pattern? I can change it then. The proposition is to return sth like Either from useCase.execute():
Future<Either<Failure, Success>> execute({
required String username,
required String password,
}) async {
if (username.isEmpty || password.isEmpty) {
// return wrapper around AuthFailure.wrongCredentials()) of Either left subtype (Either has two subtypes)
}
...
}
This way I only verify return type, and all the lines are covered this way. It's gonna work, but I feel better with the simplest, not the smartest solution.
PS I use Mocktail for mocking, but using Mockito in solution is also warmly welcomed.
If you just want to verify that the callback is triggered, I personally would just make your callback set a flag and then test that flag afterward, which I think is straightforward, simple, and easy to understand with no magic:
test('use case - failure execution for incorrect credentials', () async {
var failureCalled = false;
final user = await useCase.execute(
username: "noname",
password: "password",
onFailure: (fail) => failureCalled = true,
onSuccess: (user) {},
);
expect(user, const User.anonymous());
expect(failureCalled, true);
});
But if you really want to use Mocks, you will need some Mock object to use and to call instance methods on that in callbacks. With Mockito you could do:
test('use case - failure execution for incorrect credentials', () async {
dynamic mock = Mock();
final user = await useCase.execute(
username: "noname",
password: "password",
onFailure: (fail) => mock.fail(fail),
onSuccess: (user) {},
);
expect(user, const User.anonymous());
verify(mock.fail(any)).called(1);
});
Some things to note:
To avoid declaring a class with the expected instance methods and then code-generating stubs, create a raw Mock instance but declare it as dynamic to disable static type-checking. This will then take advantage the Mock.noSuchMethod implementation.
You can't use onFailure: mock.fail directly since the Mock has no generated stubs, and mock.fail will just be null instead of a Function.
I am not experienced with Mocktail, but I imagine that you could do something similar.

How to write tests for Either<> from dartz package in flutter

I am trying to write unit tests for a flutter app and I can't get this one test case to work correctly.
Here is the function returning Future<Either<WeatherData, DataError>>:
#override
Future<Either<WeatherData, DataError>> fetchWeatherByCity({required String city}) async {
try {
var response = await apiService.fetchWeatherByCity(city: city);
if (response.statusCode == 200) {
return Left(WeatherData.fromJson(jsonDecode(response.body)));
} else {
return Right(DataError(title: "Error", description: "Desc", code: 0, url: "NoUrl"));
}
} catch (error) {
AppException exception = error as AppException;
return Right(DataError(
title: exception.title, description: exception.description, code: exception.code, url: exception.url));
}
}
Here is the code where I am trying to write the unit test:
sut = WeatherRepositoryImpl(apiService: mockWeatherApiService);
test(
"get weather by city DataError 1 - Error 404 ",
() async {
when(mockWeatherApiService.fetchWeatherByCity(city: "city"))
.thenAnswer((_) async => Future.value(weatherRepoMockData.badResponse));
final result = await sut.fetchWeatherByCity(city: "city");
verify(mockWeatherApiService.fetchWeatherByCity(city: "city")).called(1);
expect(result, isInstanceOf<DataError>);
verifyNoMoreInteractions(mockWeatherApiService);
},
);
When I run this specific test, I receive this error:
Expected: <Instance of 'DataError'>
Actual: Right<WeatherData, DataError>:<Right(Instance of 'DataError')>
Which: is not an instance of 'DataError'
What I am not getting here? What should I be expecting from the function for the test to pass successfully?
You are directly using the result which is actually a wrapper and has a type of Either<WeatherData, DataError>.
You need to unwrap the value using the fold method on the result and then expect accordingly, So in your code you can do something like this to make it work:
final result = await sut.fetchWeatherByCity(city: "city");
result.fold(
(left) => fail('test failed'),
(right) {
expect(result, isInstanceOf<DataError>);
});
verifyNoMoreInteractions(mockWeatherApiService);
Hope this helps.
You need to either make the expected value a Right(), or extract the right side of the actual value. Doing either of those will match, but as it is, you're comparing a wrapped value with an unwrapped value.

Suspense returns data too fast in #tanstack/react-query v4

I am upgrading a React app from react-query v3 to #tanstack/react-query v4.
Almost everything works, but I'm having a problem with Suspense.
I have a react component:
const WrapperPageEdit: React.FC<MyProps> = ({
pageUuid,
redirect,
}: MyProps) => {
const FormPage = React.lazy(() => import('./FormPage'));
const { data } = usePageView(pageUuid);
if (data?.[0]) {
const pageObjectToEdit= data[0];
const content = pageObjectToEdit.myStuff.content;
return (
<Suspense
fallback={<Trans id="loading.editor">Loading the editor...</Trans>}
>
<FormPage
id={uuid}
content={content}
redirect={redirect}
/>
</Suspense>
);
}
return <p>No data.</p>;
};
And here's my query:
export function usePageView(
uuid: string,
): UseQueryResult<DrupalPage[], Error> {
return useQuery<DrupalPage[], Error>(
queryKeyUsePageView(uuid),
async () => {
return fetchAnon(getPageByPageUuid(uuid));
},
{
cacheTime: YEAR_MILLISECONDS,
staleTime: YEAR_MILLISECONDS,
onSuccess: (data) => {
if (data?.[0]) {
data.map((element) => processResult(element));
}
},
},
);
}
This works in v3 but fails in v4 with the following error:
TypeError: Cannot read properties of undefined (reading 'content')
The reason the property is undefined is because that property is set by the processing in onSuccess (data.map).
The issue appears to be that in v4, the component WrapperPageEdit is refreshed before onSuccess in the usePageView query has finished processing, whereas in v3, the component WrapperPageEdit is not refreshed until the onSuccess data.map is complete.
How can I correctly fix this? I can write some additional code to try to check whether the onSuccess data.map is complete, but since react-query handled this automatically in v3, I'd like to rewrite my code in v4 so that it is the same.
The problem is likely that you are mutating the data in onSuccess. Directly modifying data in callbacks is not a good idea. Instead, do your transformation for example directly in the queryFn:
async () => {
const data = fetchAnon(getPageByPageUuid(uuid));
if (data?.[0]) {
data.map((element) => processResult(element));
}
return data
},
other good places to do data transformation is e.g. the select option, but it should always happen in an immutable way, because otherwise, you are overwriting the cached data inadvertently. React prefers updates to be immutable.

Cannot Perform ViewFunction from near-api-js in Macos zsh but it works fine in Powershell

I've run into some error while trying to perform near-api-js viewFunction.
I've made a script to check for storage balance of an accountId parse from the API body.
When i receive the accountId parse from the API i parse into this ftGetStorageBalance function, from there the args: { account_id: accountId } is parse to accountViewFunction.
Here are the functions:
async ftGetStorageBalance(
tokenId: string,
accountId: string,
): Promise<FTStorageBalance | null> {
try {
const config = await this.getDefaultConfig();
const connection = await this.getConnect(config);
const account = await this.getAccount(this.nearCfg.accountId, connection);
return this.accountViewFunction(
{
methodName: 'storage_balance_of',
args: { account_id: accountId },
},
account,
tokenId,
);
} catch (e) {
throw new Error(e);
}
}
Here is the function when the error hits:
async accountViewFunction(
{ methodName, args }: ViewFunctionOptions,
account: nearAPI.Account,
contractId: string,
): Promise<any> {
// retrieve account_id from args
//access the first key in the args
// const key = Object.keys(args)[0];
// retrieve the value of the key
// const accountId = args[key];
// const jsonArgs = { account_id: accountId };
// const test = `{ "account_id" : "${accountId}" }`;
// const jsontest = JSON.parse(test);
// console.log(jsontest);
// const bufferArgs = Buffer.from(JSON.stringify(jsonArgs));
return account.viewFunction(contractId, methodName, args);
}
I've tried console.log the args and here's what i get:
{ account_id: 'phuocsrp3.testnet' }
In the near-api-js library, it said that the args should be wrapped in JSON.
* Invoke a contract view function using the RPC API.
* #see {#link https://docs.near.org/docs/develop/front-end/rpc#call-a-contract-function}
*
* #param contractId NEAR account where the contract is deployed
* #param methodName The view-only method (no state mutations) name on the contract as it is written in the contract code
* #param args Any arguments to the view contract method, wrapped in JSON
* #param options.parse Parse the result of the call. Receives a Buffer (bytes array) and converts it to any object. By default result will be treated as json.
* #param options.stringify Convert input arguments into a bytes array. By default the input is treated as a JSON.
* #returns {Promise<any>}
*/
viewFunction(contractId: string, methodName: string, args?: any, { parse, stringify }?: {
parse?: typeof parseJsonFromRawResponse;
stringify?: typeof bytesJsonStringify;
}): Promise<any>;
So i've tried parse into the accountViewFunction the json format with JSON.stringify(jsonArgs) stuff in the above script or even the Buffer.from(JSON.stringify(jsonArgs)) but it runs into the error stacks:
TypedError: [-32700] Parse error: Failed parsing args: missing field account_id
at /Users/phuocha/Documents/phuoc_dev/work/starpunk-crosschain-starpad/node_modules/near-api-js/lib/providers/json-rpc-provider.js:329:31
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at Object.exponentialBackoff [as default] (/Users/phuocha/Documents/phuoc_dev/work/starpunk-crosschain-starpad/node_modules/near-api-js/lib/utils/exponential-backoff.js:7:24)
at JsonRpcProvider.sendJsonRpc (/Users/phuocha/Documents/phuoc_dev/work/starpunk-crosschain-starpad/node_modules/near-api-js/lib/providers/json-rpc-provider.js:304:26)
at JsonRpcProvider.query (/Users/phuocha/Documents/phuoc_dev/work/starpunk-crosschain-starpad/node_modules/near-api-js/lib/providers/json-rpc-provider.js:116:22)
at Account.viewFunction (/Users/phuocha/Documents/phuoc_dev/work/starpunk-crosschain-starpad/node_modules/near-api-js/lib/account.js:366:24)
at NearUtilsService.singleCheckStorageAndSendToken (/Users/phuocha/Documents/phuoc_dev/work/starpunk-crosschain-starpad/src/application/near/utils/near.utils.service.ts:478:28)
at NearController.testsend (/Users/phuocha/Documents/phuoc_dev/work/starpunk-crosschain-starpad/src/application/near/near.controller.ts:58:20)
at /Users/phuocha/Documents/phuoc_dev/work/starpunk-crosschain-starpad/node_modules/#nestjs/core/router/router-execution-context.js:46:28
at /Users/phuocha/Documents/phuoc_dev/work/starpunk-crosschain-starpad/node_modules/#nestjs/core/router/router-proxy.js:9:17 {
type: 'UntypedError',
context: undefined
}
The above functions works well in Powershell but somehow it fails in macos environment.
Here is info about my env:
Nodejs version: 14.18.3
Near-api-js: 0.44.2
Nestjs: 8.0.0
The above scripts I've taken a reference from:
https://github.com/ref-finance/ref-ui/blob/main/src/services/near.ts
Please help!
We debugged this in office hours, the error was arising from using an undefined value in the contractId variable.
There are no problems with the arguments (args). You are using the token_id as the contractId when you call your viewFunction(). Maybe you can pass the correct contractId instead?
// Signature is fine. It expects a contractId, but you pass a tokenId when you call it.
accountViewFunction({ methodName, args },account,contractId){
return account.viewFunction(contractId, methodName, args);
}
this.accountViewFunction({
methodName: 'storage_balance_of',
args: { account_id: accountId },
},
account,
tokenId, // <-- you use this as contractId.
);
Try to pass the contractId instead
this.accountViewFunction({
methodName: 'storage_balance_of',
args: { account_id: accountId },
},
account,
this.contract.contractId, // <-- Use the contract's contractId
);

Flutter Unit Test - Expect difference between SuccessState<dynamic> and SuccessState<NetworkResponse>

I'm trying to make unit test for my app but I get some issues with the expect() function.
This is my test:
test('User should register.', () async {
final _response = await _api.register(mockSuccessfullRegisterUser);
mockSuccessfullLoginUser = User(email: mockSuccessfullRegisterUser.email, password: mockSuccessfullRegisterUser.password);
print(mockSuccessfullLoginUser.email);
expect(_response.runtimeType, SuccessWithTokenResponseState);
});
My _response can return multiple types of responses like: SuccessWithTokenState, ErrorState, …
The expect function gives me this error:
TestFailure (Expected: Type:<SuccessWithTokenResponseState<dynamic>>
Actual: Type:<SuccessWithTokenResponseState<NetworkResponse>>
)
Shouldn't SuccessWithTokenResponseState be equal to SuccessWithTokenResponseState?
When you use a generic type without additional type it's fallback to Type<dynamic> try to use the correct type that you expect
expect(_response.runtimeType, SuccessWithTokenResponseState<NetworkResponse>);
I found the answer!
expect(_response.runtimeType is SuccessWithTokenResponseState, true);