Flutter/Dart unit tests with app localization - flutter

I am trying to write unit tests in Dart/Flutter for my TextField validations. However, I have a little problem here because the tests are working, but I want to return the value with localization now.
How exactly do I implement this into the tests now?
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class ValidationConstants {
static String? notEmpty(String? value, BuildContext context) {
if (value == null || value.isEmpty) {
return AppLocalizations.of(context)!.text_field_can_not_be_empty;
}
return null;
}
}
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('text field validations', () {
test('no empty text validation', () {
const emptyText = '';
const noEmptyText = 'Hello, World!';
// BuildContext is needed here
expect(ValidationConstants.notEmpty(emptyText, [...]).runtimeType, String);
expect(ValidationConstants.notEmpty(noEmptyText, [...]) == null, true);
});
});
}

You can use like below, use setUp() method provided by flutter_test
void main() {
S? mockAppLocal;
setUp(() async {
mockAppLocal = await S.delegate.load(const Locale('en'));
});
}

Related

Flutter Unit Class with Initializer

I'm trying to write the unit test for this class, but I'm having problems about the initializer... When I instantiate the class in the test file, the caller throws an error that " Null check operator used on a null value". I know that's because the UserProvider is not initialized on the test folders. But how can I mock this??
class ContactController extends ChangeNotifier {
BuildContext context;
ContactController(this.context) {
initializeData();
}
late Contact contact;
initializeData() {
var userProvider = context.read<UserProvider>();
var currentContact = userProvider?.contact;
if (currentContact != null) {
newContact = currentContact;
}
notifyListeners();
}
}
The code below should do the trick. The thing is that you need to provide a mocked UserProvider into the context. To do so, just use MultiProvider in the tests to inject the mocked one.
Note the use of #GenerateMocks([UserProvider]). This comes from mockito and it annotates the source to generate the MockUserProvider class. And to generate the *.mocks.dart just run flutter pub run build_runner build.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:provider/provider.dart';
import 'package:so72756235_test_provider/contact_controller.dart';
import 'contact_controller_test.mocks.dart';
#GenerateMocks([UserProvider])
void main() {
testWidgets('ContactController test', (WidgetTester tester) async {
final mockUserProvider = MockUserProvider();
when(mockUserProvider.contact).thenReturn(const Contact(name: 'Test'));
await tester.pumpWidget(
MultiProvider(
providers: [
Provider<UserProvider>(create: (_) => mockUserProvider),
ChangeNotifierProvider(
create: (context) => ContactController(context)),
],
child: Consumer<ContactController>(
builder: (context, value, child) =>
Text(value.newContact.name, textDirection: TextDirection.ltr)),
),
);
expect(find.text('Test'), findsOneWidget);
});
}

Unit-testing function with isolates and compute in flutter

I'm trying to test a widget that receives and displays some data. This widget uses a controller. In the constructor I start receiving data, after which I execute the parser in a separate isolate. During the tests, the function passed to the compute is not executed until the end, and the widget state does not change. In fact, the structure of the widget looks a little more complicated, but I wrote smaller widget that saves my problem:
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:rxdart/rxdart.dart';
class TestObj {
int id;
String name;
String number;
TestObj(this.id, this.name, this.number);
static List<TestObj> jsonListParser(String data) {
List mapObjs = json.decode(data) as List;
if (mapObjs.isEmpty) return [];
List<TestObj> testObjs = [];
for (final Map mapObj in mapObjs as List<Map>)
testObjs.add(
TestObj(
mapObj['id'] as int,
mapObj['name'] as String,
mapObj['number'] as String,
),
);
return testObjs;
}
}
class TestController {
final BehaviorSubject<List<TestObj>> testSubj;
final String responseBody =
'[{"id":2,"number":"1","name":"Объект 1"},{"id":1,"number":"2","name":"Объект 2"}]';
TestController(this.testSubj) {
getData(responseBody, testSubj);
}
Future<void> getData(
String responseBody, BehaviorSubject<List<TestObj>> testSubj) async {
List<TestObj> data = await compute(TestObj.jsonListParser, responseBody);
testSubj.sink.add(data);
}
}
class TestWidget extends StatelessWidget {
final BehaviorSubject<List<TestObj>> testSubj;
final TestController controller;
const TestWidget(this.testSubj, this.controller);
#override
Widget build(BuildContext context) {
return StreamBuilder<List<TestObj>>(
stream: testSubj.stream,
builder: (context, snapshot) => snapshot.data == null
? const CircularProgressIndicator()
: ListView.builder(
itemBuilder: (context, index) => Text(snapshot.data[index].name),
),
);
}
}
void main() {
testWidgets('example test', (tester) async {
final BehaviorSubject<List<TestObj>> testSubj =
BehaviorSubject.seeded(null);
final TestController testController = TestController(testSubj);
await tester.pumpWidget(
TestWidget(testSubj, testController),
);
expect(find.byType(CircularProgressIndicator), findsNothing);
});
}
I have tried using tester.pump, tester.pumpAndSettle (crashed by timeout) and tester.runAsync, but so far without success. What are the solutions of this problem?
As indicated in runAsync docs, it is not supported to have isolates/compute in tests that are proceeded by pump().
To make a self-contained solution, check if you run in test environment or not in your code and skip isolates when you run in a test:
import 'dart:io';
if (!kIsWeb && Platform.environment.containsKey('FLUTTER_TEST')) {
calc()
} else {
calcInIsolate()
}

Flutter bloc test -- emitsInorder does not emit the initial state

I'm really confused of how to get the bloc to emit the initial state. bloc.state emits the latest state. And since there is no #override initialState available in the new bloc library, initialState was passed in to the super constructor. But still bloc does not emit the initialState, which in this case is Empty().
number_trivia_bloc.dart
import 'package:clean_architecture/core/error/failure.dart';
import 'package:clean_architecture/core/utils/input_converter.dart';
import 'package:clean_architecture/features/number_trivia/domain/usecases/get_concrete_number_trivia_repository.dart';
import 'package:clean_architecture/features/number_trivia/domain/usecases/get_random_number_trivia.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'number_trivia_state.dart';
import 'number_trivia_event.dart';
const SERVER_FAILURE = 'Server failure';
const CACHE_FAILURE = 'Cache failure';
const INVALID_INPUT_FAILURE =
'Invalid Input Failure - The input should not be a negative integer or zero';
class NumberTriviaBloc extends Bloc<NumberTriviaEvent, NumberTriviaState> {
final GetConcreteNumberTrivia getConcreteNumberTrivia;
final GetRandomNumberTrivia getRandomNumberTrivia;
final InputConverter inputConverter;
NumberTriviaBloc({
// Changed the name of the constructor parameter (cannot use 'this.')
#required GetConcreteNumberTrivia concrete,
#required GetRandomNumberTrivia random,
#required this.inputConverter,
// Asserts are how you can make sure that a passed in argument is not null.
// We omit this elsewhere for the sake of brevity.
}) : assert(concrete != null),
assert(random != null),
assert(inputConverter != null),
getConcreteNumberTrivia = concrete,
getRandomNumberTrivia = random,
super(Empty());
#override
Stream<NumberTriviaState> mapEventToState(
NumberTriviaEvent event,
) async* {
if (event is GetTriviaForConcreteNumber) {
final inputEither =
inputConverter.stringToUnsignedInteger(event.numberString);
yield* inputEither.fold(
(failure) async* {
yield Error(message: INVALID_INPUT_FAILURE);
},
(integer) async* {
yield Loading();
},
);
}
}
}
number_trivia_bloc_test.dart
import 'package:clean_architecture/core/utils/input_converter.dart';
import 'package:clean_architecture/features/number_trivia/data/models/number_trivia_model.dart';
import 'package:clean_architecture/features/number_trivia/domain/entities/number_trivia.dart';
import 'package:clean_architecture/features/number_trivia/domain/usecases/get_concrete_number_trivia_repository.dart';
import 'package:clean_architecture/features/number_trivia/domain/usecases/get_random_number_trivia.dart';
import 'package:clean_architecture/features/number_trivia/presentation/bloc/number_trivia_bloc.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:clean_architecture/features/number_trivia/presentation/bloc/number_trivia_state.dart';
import 'package:clean_architecture/features/number_trivia/presentation/bloc/number_trivia_event.dart';
import 'package:mockito/mockito.dart';
class MockGetConcreteNumberTrivia extends Mock
implements GetConcreteNumberTrivia {}
class MockGetRandomNumberTrivia extends Mock implements GetRandomNumberTrivia {}
class MockInputConverter extends Mock implements InputConverter {}
void main() {
NumberTriviaBloc bloc;
MockGetConcreteNumberTrivia mockGetConcreteNumberTrivia;
MockGetRandomNumberTrivia mockGetRandomNumberTrivia;
MockInputConverter mockInputConverter;
setUp(() {
mockGetConcreteNumberTrivia = MockGetConcreteNumberTrivia();
mockGetRandomNumberTrivia = MockGetRandomNumberTrivia();
mockInputConverter = MockInputConverter();
bloc = NumberTriviaBloc(
concrete: mockGetConcreteNumberTrivia,
random: mockGetRandomNumberTrivia,
inputConverter: mockInputConverter,
);
});
test('initialState should be Empty', () {
// assert
expect(bloc.state, equals(Empty()));
});
group('GetTriviaForNumber', () {
String str = '1';
int parsedStr = int.parse(str);
NumberTrivia tTrivia =
NumberTriviaModel(text: 'test text', number: parsedStr);
test('Should convert a string to an unsigned integer', () async {
//arrange
when(mockInputConverter.stringToUnsignedInteger(any))
.thenReturn(Right(parsedStr));
//act
bloc.add(GetTriviaForConcreteNumber(str));
await untilCalled(mockInputConverter.stringToUnsignedInteger(str));
//assert
verify(mockInputConverter.stringToUnsignedInteger(any));
});
test('Should return [Error] for InvalidInputFailure', () async {
//arrange
when(mockInputConverter.stringToUnsignedInteger(any))
.thenReturn(Left(InvalidInputFailure()));
//assert later
final expected = [
Empty(),
Error(message: INVALID_INPUT_FAILURE),
];
expectLater(bloc, emitsInOrder(expected));
//act
bloc.add(GetTriviaForConcreteNumber(str));
});
});
}
It should work but all I get is this error screaming at me. I tries bloc.cast(), bloc.asBroadcastStream(). Nothing seems to work.So, please, can someone help me figure this out. Thanks in advance.
There contains some change from flutter_bloc 6.0.0
You can check the initial state via
test('initial state is correct', () {
expect(bloc.state, Empty());
});
Mentioned here:
Regression: initial state is not emited anymore

keep an integer with SharedPreferences in Flutter

I want to make a one time page only first time this page will open and ı don't know how to keep this command. I think I should use SharedPreferences but couldn't do that. Please help me.
import 'package:eventer/landing/second.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class LandingPage extends StatelessWidget {
int index = 0;
#override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
onPressed: selecter(),
),
);
}
selecter() {
if (index == 0) {
return FirstPage();
} else {
() {
return SecondPage();
};
}
}
}
you can install sharedpreference dependency using pubspec.yaml file
shared_preferences: ^0.5.4
then in your .dart file import package
import 'package:shared_preferences/shared_preferences.dart';
you can store info in sharedpreference like below
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setInt('index', index);
then you can retrieve value like this
var index_value = prefs.getInt('index');
then you can perform your condition accordingly
Use this plugin
Add dependencies to pubspec.yaml file.
shared_preferences: ^0.5.7+3
Import shared_preferences.dart
import 'package:shared_preferences/shared_preferences.dart';
selecter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int index = await prefs.getInt('counter') ?? 0;
if (index == 0) {
await prefs.setInt('counter', 1;
return FirstPage();
} else {
return SecondPage();
}
}

Detect Mock Location is enabled or disabled in Flutter

My question is that I am using flutter platform to develop an app for my client and I want that my developed app should be able to be detect mock location status from android phone settings so I can check whether the location is coming from gps provider or mock location app. And if mock location is enabled then my app should throw an error msg
i had the same problem and i fixed it by coding in java and implement in flutter project.
here is what i did:
1) add this to your Main_Activity in flutter project.
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import java.util.List;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "samples.flutter.io/location";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodCallHandler() {
#Override
public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("getLocation")) {
boolean b = getMockLocation();
result.success(b);
} else {
result.notImplemented();
}
}
});
}
public static boolean isMockSettingsON(Context context) {
// returns true if mock location enabled, false if not enabled.
if (VERSION.SDK_INT >= VERSION_CODES.CUPCAKE) {
if (Settings.Secure.getString(context.getContentResolver(),
Settings.Secure.ALLOW_MOCK_LOCATION).equals("0"))
return false;
else
return true;
}
return false;
}
public static boolean areThereMockPermissionApps(Context context) {
int count = 0;
PackageManager pm = context.getPackageManager();
List<ApplicationInfo> packages =
pm.getInstalledApplications(PackageManager.GET_META_DATA);
for (ApplicationInfo applicationInfo : packages) {
try {
PackageInfo packageInfo = pm.getPackageInfo(applicationInfo.packageName,
PackageManager.GET_PERMISSIONS);
// Get Permissions
String[] requestedPermissions = packageInfo.requestedPermissions;
if (requestedPermissions != null) {
for (int i = 0; i < requestedPermissions.length; i++) {
if (requestedPermissions[i]
.equals("android.permission.ACCESS_MOCK_LOCATION")
&& !applicationInfo.packageName.equals(context.getPackageName())) {
count++;
}
}
}
} catch (PackageManager.NameNotFoundException e) {
Log.e("Got exception " , e.getMessage());
}
}
if (count > 0)
return true;
return false;
}
private boolean getMockLocation() {
boolean b ;
b= areThereMockPermissionApps(MainActivity.this);
return b;
}
}
2) use it in your flutter_dart Code like this:
static const platform = const MethodChannel('samples.flutter.io/location');
bool mocklocation = false;
Future<void> _getMockLocation() async {
bool b;
try {
final bool result = await platform.invokeMethod('getLocation');
b = result;
} on PlatformException catch (e) {
b = false;
}
mocklocation = b;
}
if (mocklocation == true) {
return showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return WillPopScope(
onWillPop: (){},
child: AlertDialog(
title: Text('Location'),
content: Text('Your Location is fake'),
),
);
});
}
3) for more information and example:
https://flutter.dev/docs/development/platform-integration/platform-channels
Barzan's answer is very good, there's also a Flutter package named trust_location, you can find it here.
You can use it as following to check mock location:
bool isMockLocation = await TrustLocation.isMockLocation;
So, I recommend to use it.