currently I am writing Unit tests for my mobile web application (Android app). The unit tests refer to my login system built upon Google Firebase Auth.
With one of the latest releases of the firebase authentication plugins, the return type of some functions have been changed. For example, the method firebaseAuth.signInWithEmailAndPassword has been changed from FirebaseUser to AuthResult
I have a login function in a AuthService.dart with the following content:
Future<bool> login(LoginUserData userData) async {
try {
_checkUserDataValidity(userData);
final AuthResult authResult= await firebaseAuth.signInWithEmailAndPassword(email: userData.email, password: userData.password);
return authResult.user != null;
} catch (exception) {
return false;
}
}
So, as you can see I am only checking whether the data of the user is correct or not - I am returning a boolean to check this in another file (if true is returned, the Navigator routes to another site, if wrong is returned an error message pops up).
My Unit tests for example checks for a few cases:
Correct data
Incorrect data (userData is not empty or null)
Empty data
Example code of a test:
test('Login should work with valid user data', () async {
when(
firebaseAuthMock.signInWithEmailAndPassword(
email: testValidUserData.email,
password: testValidUserData.password,
)
).thenAnswer(
(_) => Future<AuthResultMock>.value(firebaseAuthResultMock)
);
final bool actionSuccess = await authService.login(testValidUserData);
verify(
firebaseAuthMock.signInWithEmailAndPassword(
email: testValidUserData.email,
password: testValidUserData.password,
)
).called(1);
expect(actionSuccess, true);
});
To cover case one and two of my list above, I'd like to use two different Mocks. One Mock to contain a user, the other mock has no user in it.
Current issue:
(_) => Future<AuthResultMock>.value(firebaseAuthResultMock)
in my test is in conflict with this code from the AuthServie.dart
return authResult.user != null;
The mock is having null as user. How can I change this? :) Bt.w I am using Mockito.
My idea was:
For correct data, to return a mock, that contains a user.
For invalid data, to return a mock, that has null as user.
Thank you so much. :)
I was just recently looking for the answer and stumbled across your question. But in the meanwhile, I found a solution. I hope this works for you.
I found that there are a bunch of flutter packages for mocking firebase, the one for Firebase Auth is this one: https://pub.dev/packages/firebase_auth_mocks.
Also if you don't want to import more packages into your code you can create a simpler version of them. Like so:
class MockFirebaseAuth extends Mock implements FirebaseAuth {}
class MockAuthResult extends Mock implements AuthResult {
FirebaseUser user = MockFirebaseUser();
}
class MockFirebaseUser extends Mock implements FirebaseUser {
#override
String get displayName => 'Test Name';
#override
String get uid => 'abc1234';
}
Related
I'm trying to implement a getter, but it is showing me this error on last line in the below code snippet.
The code is -
class AuthRepository extends BaseAuthRepository {
final FirebaseFirestore _firebaseFirestore;
final auth.FirebaseAuth _firebaseAuth;
AuthRepository({
FirebaseFirestore? firebaseFirestore,
auth.FirebaseAuth? firebaseAuth,
}) : _firebaseFirestore = firebaseFirestore ?? FirebaseFirestore.instance,
_firebaseAuth = firebaseAuth ?? auth.FirebaseAuth.instance;
#override
// TODO: implement user
Stream<auth.User> get user => _firebaseAuth.userChanges();
try this
Stream<auth.User?> get user => auth.userChanges();
change
Stream<auth.User> get user => _firebaseAuth.userChanges();
to
Stream<auth.User> get user => _firebaseAuth.userChanges()!;
As can be seen in the documentation,
The method returns an object of Stream<User?>, which means that it may be null.
Since you can't be sure of the contents of the type returned, instead of returning it, you can check if it is null.
I.E.
class AuthRepository extends BaseAuthRepository {
final FirebaseFirestore _firebaseFirestore;
final auth.FirebaseAuth _firebaseAuth;
AuthRepository({
FirebaseFirestore? firebaseFirestore,
auth.FirebaseAuth? firebaseAuth,
}) : _firebaseFirestore = firebaseFirestore ?? FirebaseFirestore.instance,
_firebaseAuth = firebaseAuth ?? auth.FirebaseAuth.instance;
#override
// TODO: implement user
Stream<auth.User> get user =>
val userChanges = _firebaseAuth.userChanges();
if (userChanges != null) return userChanges
else //your logic
_firebaseAuth.userChanges() returns User? which is nullable so you need to update your return type to be nullable by adding ? after auth.User like below:
Stream<auth.User?> get user => _firebaseAuth.userChanges();
This it worked for me:
Stream<auth.User?> get user => _firebaseAuth.userChanges();
It sends out events when a user's credentials are linked or unlinked, as well as when the user's profile is updated. This Stream's goal is to listen to real-time changes to the user state (signed-in, signed-out, different user & token refresh) without having to manually perform reload and then return changes to your application.
Check the documentation for more information.
I recently started developing an app using Flutter and Firebase. I use Firebase Emulator to test Authentication and Cloud Functions. Most of my code is in the Firebase Cloud Functions which I use for all CRUD for Firestore and RTDB. While adding some new features, I got this error in my app. I tried searching a lot but could not find any solution. The following is the error is receive:
An error occured while calling function profile-get
Error Details: null
Message: An internal error has occurred, print and inspect the error details for more information.
Plugin: firebase_functions
Stacktrace: null
My API class in Flutter:
class Api {
Api(this.functions);
final FirebaseFunctions functions;
static Api init() {
FirebaseFunctions functions = FirebaseFunctions.instance;
if (emulator) functions.useFunctionsEmulator(origin: host);
return Api(functions);
}
Future<ApiResult> call(String name, {
Map<String, dynamic> parameters,
}) async {
try {
HttpsCallable callable = functions.httpsCallable(name);
HttpsCallableResult results = await callable.call(parameters);
return ApiResult(new Map<String, dynamic>.from(results.data));
} on FirebaseFunctionsException catch (e) {
print('An error occurred while calling function $name.');
print('Error Details: ${e.details}\nMessage: ${e.message}\nPlugin: ${e.plugin}\nStacktrace: ${e.stackTrace}');
return ApiResult({
'status': 'error',
'message': 'An error occured',
'code': 'unknown'
});
}
}
static String get host => Platform.isAndroid ? 'http://10.0.2.2:2021' : 'http://localhost:2021';
}
I tried running the functions directly from their local URL and they work fine.
As mentioned in the comments defore you are reating a cloud function with onRequest. Those are not callable using an SDK but only trough https URL.
To create a callable function that you can call trough Firebase SDKs you would need to refactor your functions to use the onCall.
It should look something like this:
exports.yourFunctionName= functions.https.onCall((data, context) => {
// receive the data
const text = data.text;
// return a response
return {
test:'test'
}
});
Here you have more information how the callable functions work.
Are you using a different region than the standard us-central1? This is often the case, so you need to change the region you are calling from
HttpsCallable callable = FirebaseFunctions.instanceFor(region:"your_region").httpsCallable(name);
So, In my flutter app, I am trying to add functionality to change email.
I used userData.updateEmail(email) method, but it gives this error:
Unhandled Exception: PlatformException(ERROR_REQUIRES_RECENT_LOGIN, This operation is sensitive and requires recent authentication. Log in again before retrying this request., null)
On surfing for a solution on the Internet I got to know, I need to reauthenticate user by this method:
userData.reauthenticateWithCredential(credential)
But I can't find a way to get credential to pass to reauthenticateWithCredential method.
Some code snippets (tho I feel they are unnecessary):
initUserData() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
setState(() {
userData = user;
});
}
updateEmail(String value) async {
// value is the email user inputs in a textfield and is validated
userData.updateEmail(value);
}
Note: I am using both login with google and password-email login.
There is a re-authenticate method. You just need to obtain the user's password for calling the method.
FirebaseUser user = await FirebaseAuth.instance.currentUser();
AuthResult authResult = await user.reauthenticateWithCredential(
EmailAuthProvider.getCredential(
email: user.email,
password: password,
),
);
// Then use the newly re-authenticated user
authResult.user
I had this problem too and I found the solution. You can get the AuthCredential for the providers you're using like this:
EmailAuthProvider.getCredential(email: 'email', password: 'password');
and
GoogleAuthProvider.getCredential(idToken: '', accessToken: '')
Both methods return what you're looking for.
It is working for me
Future resetEmail(String newEmail) async {
var message;
FirebaseUser firebaseUser = await _firebaseAuth.currentUser();
firebaseUser
.updateEmail(newEmail)
.then(
(value) => message = 'Success',
)
.catchError((onError) => message = 'error');
return message;
}
When you want to change sensitive informations on Firebase you need to re-authenticate first to your account using your current credentials then you can update it.
Currently flutter has no reAuthenticate method for Firebase so you need to call signInWithEmailAndPassword or any other signIn method.
I am making an app in ionic 3 and I am trying to fetch data from an url.
This is my code in data.ts and my data provider:
getVehicleStatus() {
return this.http.get<VehicleStatus>(this.url);
}
This is my vehicle class:
class VehicleStatus {
status: string;
plate: string;
code: string;
message: string;
}
I am calling the method into my home.ts file.
ionViewDidLoad() {
console.log(this.getStatus('test3'));
}
getStatus(plate: string) {
this.dataService.getVehicleStatus()
.subscribe((data: VehicleStatus) => this.vehiclestatus = [{ ...data }]);
}
To test out if everything works I hard code a license plate number to log it into my chrome developer tools. It said 'Undefined'.
This is how the json data looks like:
[{"status":"P","plate":"test2","code:"MGP150151","message":"fail"}
,{"status":"P","plate":"test3","code":"MGP160298","message":"fail"}
,{"status":"P","plate":"test4","code":"MGP140085","message":"succes"}
,{"status":"O","plate":"test5","code":"MGP150175","message":"succes"}]
I should get this object back:
{"status":"P","plate":"test3","code":"MGP160298","message":"fail"}
But it doesn't work and got the message undefined.
I have used the following source:
https://angular.io/guide/http
How can I search in the array and bind it to my HTML page in ionic 3?.
Can someone point me in the right direction?.
Kind regards .
The responsibility to find the VehicleStatus from the list should be of the service rather than of the Component itself.
Consider changing your Service Implementation to take up that responsibility. You can use the map operator to transform the response to return the VehicleStatus found based on the plate that will be passed as an arg while calling the getVehicleStatus method.
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
getVehicleStatus(plate): Observable<VehicleStatus> {
return this.http.get<VehicleStatus[]>(this.url)
.pipe(
map(statuses => statuses.find(status => status.plate === plate))
);
}
Then in your home.ts:
ionViewDidLoad() {
this.getStatus('test3');
}
getStatus(plate: string) {
this.dataService.getVehicleStatus(plate)
.subscribe((data: VehicleStatus) => this.vehiclestatus = data);
}
You need array.find to get the matching value, which will return the first matching element from the array
this.vehiclestatus = data.find(vehicle=>vehicle.plate === plate);
I have a strange error on my react-native Firebase APL.
react-native run-android works fine without error.
but react-native run-ios failed with JSON value of type NSnull cannot be converted to NSString.
source code is as follows.(main and signup class to authentication work on firebase)
I think Firebase Class has different ACT on ios and Android to convert JSON to text.
Any suggestion appreciated.
Shoji
main
// Initialize the firebase app here and pass it to other components as needed. Only initialize on startup.
const firebaseApp = firebase.initializeApp(firebaseConfig);
var GiftedMessenger = require('react-native-gifted-messenger');
let styles = {}
class Pricing extends Component {
constructor(props){
super(props);
this.state = {
page: null
};
/* this.itemsRef = this.getRef().child('items'); */
}
componentWillMount(){
// We must asynchronously get the auth state, if we use currentUser here, it'll be null
const unsubscribe = firebaseApp.auth().onAuthStateChanged((user) => {
// If the user is logged in take them to the accounts screen
if (user != null) {
this.setState({page: Account});
console.log('(user != null)')
return;
}
// otherwise have them login
console.log('(user != Login)')
this.setState({page: Login});
// unsubscribe this observer
unsubscribe();
});
}
render() {
if (this.state.page) {
return (
// Take the user to whatever page we set the state to.
// We will use a transition where the new page will slide in from the right.
<Navigator
initialRoute={{component: this.state.page}}
configureScene={() => {
return Navigator.SceneConfigs.FloatFromRight;
}}
renderScene={(route, navigator) => {
if(route.component){
// Pass the navigator the the page so it can navigate as well.
// Pass firebaseApp so it can make calls to firebase.
return React.createElement(route.component, { navigator, firebaseApp});
}
}} />
);
} else {
return (
// Our default loading view while waiting to hear back from firebase
I deeply debug in this BUG.
At last I found it as follows.
<Image
source={{uri: 'this.state.user.photoURL'}} <<<single quotation was added.
style={styles.image}
/>
I did not expect like this kind of error.
Thanks Shoji
took help from #itagaki. Error was removed after replacing single quote with double quote in following code
const BoldText = (props) => <Text style={{fontWeight: 'bold'}}>{props.children}</Text>;