for learning purposes I am trying to mock a Firestore controller class with Mockito.
firestore_controller.dart
import 'package:cloud_firestore/cloud_firestore.dart';
class FirestoreController implements FirestoreControllerInterface {
final Firestore firestoreApi;
FirestoreController({this.firestoreApi});
#override
Future<DocumentSnapshot> read() async {
final DocumentSnapshot document = await this.firestoreApi.collection('user').document('user_fooBar').get();
return document;
}
}
firestore_controller_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:mockito/mockito.dart';
import 'package:fooApp/Core/repositories/firebase/firestore_controller.dart';
class MockFirestoreBackend extends Mock implements Firestore {}
class MockDocumentSnapshot extends Mock implements DocumentSnapshot {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group("Firebase Controller", () {
final Firestore firestoreMock = MockFirestoreBackend();
final MockDocumentSnapshot mockDocumentSnapshot = MockDocumentSnapshot();
FirestoreController sut;
test("try to read a document", () async {
// Arrange
final Map<String, dynamic> _fakeResponse = {
'foo': 123,
'bar': 'foobar was here',
};
sut = FirestoreController(firestoreApi: firestoreMock); // INJECT MOCK
// Arrange: Mock
when(firestoreMock.collection('user').document('user_fooBar').get()).thenAnswer((_) => Future<MockDocumentSnapshot>.value(mockDocumentSnapshot));
when(mockDocumentSnapshot.data).thenReturn(_fakeResponse);
// Act
final fakeDocument = await sut.read();
});
});
}
🚨 Console Output 🚨
NoSuchMethodError: The method 'document' was called on null.
Receiver: null
Tried calling: document("user_fooBar")
sorry if the mistake is obvious, this is the first time I've used Mockito
Where's my error? What do I miss? Thanks a lot!
try this:
https://pub.dev/packages/cloud_firestore_mocks
A test I've done using it:
class MockFirestore extends Mock implements Firestore {}
class MockDocument extends Mock implements DocumentSnapshot {}
void main() {
final _tAppointmentModel = AppointmentModel(
appointmentID: kAppointmentID,
date: DateTime.parse("2020-12-05 20:18:04Z"),
description: "test description",
doctorID: kDoctorID,
hospitalID: kHospitalID,
infantID: kInfantID,
);
group('AppointmentModel tests: ', () {
final tAppointmentID = kAppointmentID;
final tInfantID = kInfantID;
final tDoctorID = kDoctorID;
final tHospitalID = kHospitalID;
test('should be a subclass of Appointment', () async {
expect(_tAppointmentModel, isA<Appointment>());
});
test('store and retrieve document from Firestore', () async {
final instance = MockFirestoreInstance();
await instance.collection('appointments').add({
'appointmentID': tAppointmentID,
'date': DateTime.parse("2020-12-05 20:18:04Z"),
'description': "test description",
'doctorID': tDoctorID,
'hospitalID': tHospitalID,
'infantID': tInfantID,
});
final snapshot = await instance.collection('appointments').getDocuments();
final String expected = _tAppointmentModel.props[0];
final String result = snapshot.documents[0].data['appointmentID'];
expect(expected, result);
});
});
}
So I think I've found why this is - it appears to be a bug(ette) in Mockito, in that it doesn't handle the "dot walk" from collection(any) to document() or getDocuments(). I fixed it like this:
declare five classes:
class MockFirebaseClient extends Mock implements Firestore {} //for your mock injection
class MockCollectionReference extends Mock implements CollectionReference {} //for when declaration
class MockQuerySnapshot extends Mock implements QuerySnapshot {} //for the thenAnswer return on collection of docs
class MockDocumentReference extends Mock implements DocumentReference {} //for single doc query
class MockDocumentSnapshot extends Mock implements DocumentSnapshot {} // for the thenAnswer return on single doc query
Do your setup etc - then the when clauses are just:
when(mockCollectionReference.getDocuments())
.thenAnswer((_) => Future.value(mockQuerySnapshot)); //for collection of docs query
when(mockDocumentReference.get())
.thenAnswer((_) => Future.value(mockDocumentSnapshot)); //for single doc query
Related
I have a normal class like this
class University implements BaseResponseContract {
final String? name;
final int? id;
University({required this.id, required this.name});
factory University.fromJson(Map<String, dynamic> json) => _$UniversityFromJson(json);
Map<String, dynamic> toJson() => _$UniversityToJson(this);
}
and this is BaseResponseContract
abstract class BaseResponseContract<T> {
factory T.fromJson(Map<String, dynamic> json);
}
This gives an error now, I want to correct and
I want to build a general Firebase class to read datas so that I dont have to write from scratch everytime,
abstract class IFirebaseEntity<T> {
final String collectionName;
late final instance;
IFirebaseEntity(this.collectionName) {
instance = FirebaseFirestore.instance.collection(collectionName)
.withConverter(fromFirestore: (snapshot,_)=>**T.fromJson(snapshot)**, toFirestore: toFirestore);
}
However it doesn't work. It doesn't recognize T.fromJson() method,
I want to build a Firebase Entity class to build such a functionality that, I will extend with a T and will be able to use that firebase class generated by the abstract class.
This is an example that extends this FirebaseEntity
class ReadUnivercities extends IFirebaseEntity<University> {
ReadUnivercities(String collectionName) : super(collectionName);
}
In my main.dart I get the following error:
The argument type 'UserRepository/1/' can't be assigned to the parameter type 'UserRepository/2/'
The code is the following:
void main() {
WidgetsFlutterBinding.ensureInitialized();
final UserRepository _userRepository = UserRepository();
BlocSupervisor.delegate = SimpleBlocDelegate();
runApp(BlocProvider(
create: (context) => AuthenticationBloc(userRepository: _userRepository)
..add(AppStarted()),
child: Home(userRepository: _userRepository)));
}
And the class AuthenticationBloc looks like this:
class AuthenticationBloc
extends Bloc<AuthenticationEvent, AuthenticationState> {
final UserRepository _userRepository;
AuthenticationBloc({#required UserRepository userRepository})
: assert(userRepository != null),
_userRepository = userRepository;
#override
AuthenticationState get initialState => Uninitialized();
...
How can I fix this issue?
I've had the same error when i had two imports like this:
import 'package:app/repository.dart';
// In other file
import 'package:app//repository.dart';
Because of that second import which has double '/' it was throwing an error.
What is recommended/best way to validate the post request DTO bean ?
If validation failed I need to send customized error message like
{
"code": "invalid_fields",
"fields": {
"email": "Required",
"password": "Required",
}
}
DTO model
public class SignUpRequest {
#JsonProperty("email")
String email;
#JsonProperty("password")
String password;
public Result validate(){
}
}
controller
#PostMapping(value = "/register")
public ResponseEntity<Object> signupRider(#RequestBody SignUpRequest signUpRequest) {
Result result = signUpRequest.validate();
return new ResponseEntity<>(x, HttpStatus.OK);
}
SignUpRequest DTO has the method validate.
What is the spring way of doing the validation ?
Thanks.
You can use the following technique.
add the following dependencies in your gradle/maven file
compile "javax.validation:validation-api:2.0.1.Final"
compile "org.hibernate.validator:hibernate-validator:6.0.9.Final"
Hibernate-validator is implementation of validation-api 2.0
Add Validated annotation to your controller class
import org.springframework.validation.annotation.Validated;
#RestController
#RequestMapping(value = "/contact")
#Validated
public class ContactController{
}
Add Valid annotation to your method parameter
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
#RestController
#RequestMapping(value = "/contact")
#Validated
public class ContactController{
#PostMapping(value = "/register")
public ResponseEntity<Object> signupRider(#Valid #RequestBody SignUpRequest signUpRequest) {
Result result = signUpRequest.validate();
return new ResponseEntity<>(x, HttpStatus.OK);
}
}
Add Validated annotation to your dto class
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Email;
#Validated
public class SignUpRequest {
#JsonProperty("email")
#Email
String email;
#JsonProperty("password")
#NotNull
String password;
}
Add ExceptionTranslator with RestControllerAdvice annotation
#RestControllerAdvice
public class ExceptionTranslator {
/**
* Exception handler for validation errors caused by method parameters #RequesParam, #PathVariable, #RequestHeader annotated with javax.validation constraints.
*/
#ExceptionHandler
protected ResponseEntity<?> handleConstraintViolationException(ConstraintViolationException exception) {
List<ApiError> apiErrors = new ArrayList<>();
for (ConstraintViolation<?> violation : exception.getConstraintViolations()) {
String value = (violation.getInvalidValue() == null ? null : violation.getInvalidValue().toString());
apiErrors.add(new ApiError(violation.getPropertyPath().toString(), value, violation.getMessage()));
}
return ResponseEntity.badRequest().body(apiErrors);
}
}
Create ApiError class
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#NoArgsConstructor
#AllArgsConstructor
public class ApiError {
#JsonIgnore
private int code;
private String field;
private String value;
private String message;
public ApiError(String message) {
this.message = message;
}
public ApiError(String field, String value, String message) {
this.field = field;
this.value = value;
this.message = message;
}
}
Now if password field is missed you'll see the following response structure:
[
{
"field": "password",
"message": "must be filled"
}
]
If you would like to use some custom logic to validate your fields you may use the following approach
Create specific annotation class
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Constraint(validatedBy = ContactRequiredParametersValidator.class)
#Target({ METHOD, CONSTRUCTOR })
#Retention(RUNTIME)
#Documented
public #interface ContactRequiredParameters {
String message() default
"Email or phone must be filled";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Create custom validator
import org.apache.commons.lang.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.constraintvalidation.SupportedValidationTarget;
import javax.validation.constraintvalidation.ValidationTarget;
#SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class ContactRequiredParametersValidator implements ConstraintValidator<ContactRequiredParameters, Object[]> {
#Override
public boolean isValid(Object[] value,
ConstraintValidatorContext context) {
if (value[0] == null) {
return true;
}
if (!(value[0] instanceof SignUpRequest)) {
throw new IllegalArgumentException(
"Illegal method signature, expected two parameters of type LocalDate.");
}
SignUpRequest contact = (SignUpRequest) value[0];
return StringUtils.isNotEmpty(contact.getPassword());
}
}
add #ContactRequiredParameters annotation to your method in controller
#PostMapping(value = "/register")
#ContactRequiredParameters
public ResponseEntity<Object> signupRider(#Valid #RequestBody SignUpRequest signUpRequest)
That's all. Hope it helps
Spring boot supports validation out of the box using validation-api which is included with spring web mvc starter:
#RestController
#RequiredArgsConstructor
public class TestController {
#PutMapping(value = "/", consumes = APPLICATION_JSON_VALUE)
#ResponseStatus(NO_CONTENT)
public void test(#Valid #RequestBody final SignUpRequest params) {
...
}
}
You can annotate your SignUpRequest using annotations such as javax.validation.constraints.NotNull and other more complex ones.
the error messages can be customised with message properties or hard coded strings if i18n/l10n is of less interest to you.
Sample here: https://spring.io/guides/gs/validating-form-input/
If you want behaviour outside of the provided annotations you can write a custom annotation that can do that, e.g.
#Target({FIELD})
#Retention(RUNTIME)
#Constraint(validatedBy = NotPastValidator.class)
#Documented
public #interface NotPast {
String message() default "date must not be in the past";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Then:
public class NotPastValidator implements ConstraintValidator<NotPast, LocalDate> {
#Override
public void initialize(final NotPast constraintAnnotation) {
// nothing to do.
}
#Override
public boolean isValid(final LocalDate value, final ConstraintValidatorContext context) {
// As the Bean Validation specification recommends, we consider null values as being valid.
return value == null || isDateNotPast(value);
}
private boolean isDateNotPast(final LocalDate value) {
return ...
}
}
And finally just annotate your field:
#NotPast
Of course this is just an example with some code I previously used, you'll need to adapt to your needs.
If you don't want to use the validator API at all you can equally just write your own code to programatically check and throw some type of custom exception when invalid. This can then be caught in the controller and you can send what ever response you want, e.g.
#RestController
public class PaymentController {
#PostMapping(value ="/", consumes = APPLICATION_JSON_VALUE)
public void makePayment(#RequestBody final PaymentParams params) {
// validationService.validate(params);
}
#ExceptionHandler(MyValidationException.class)
public ResponseEntity<ExceptionDto> paymentCardException(final MyValidationException e) {
return status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(new ExceptionDto(e.getMessage));
}
}
I'd say given the validation API is well supported by spring, to me it makes sense to apply declarative validations where possible when using this stack. Custom rules can be a little painful, but you can use a multi faceted approach with some annotation based and equally you can perform some more complex validations in your own service.
This is a custom validation.
#PostMapping
private ResponseEntity<?> addMessage(#RequestBody Message message) {
Map<String, String> response = new HashMap<>();
if (message.getInputMessage() == null || message.getInputMessage().equals("")) {
response.put("status", "E");
response.put("message", "input message can not be empty");
return ResponseEntity.ok(response);
}
int id = messageService.addMessage(message);
if (id <= 0) {
response.put("status", "E");
response.put("message", "add message has error");
return ResponseEntity.ok(response);
}
response.put("status", "S");
response.put("message", "success");
return ResponseEntity.ok(response);
}
constructor(public navCtrl: NavController,public navParams : NavParams,
public modalCtrl:ModalController, private afAuth:AngularFireAuth, private afDatabase:AngularFireDatabase , public fb:FirebaseService) {
this.getDefaults();
this.selectedExercise=[];
console.log("home");
this.exercises=this.fb.getShoppingItems();
console.log(this.exercises);
at home.ts file,
at constructor, I get firebase data from firebase provider that I made to show it on list on html.
but the problem is that I logged it console.log(this.exercises) which is the data that I want to show on list, but it came null.
but after logged null, firebase provider get data from firebase and logged it
as you can see below.
I think browser should wait until firebase provider get data then should list it.
but don't know how to do it.
This is the way you should go
You need to change your getShoppingItems method to return a promise object like this
getShoppingItems(){
return new Promise<any>((resolve, reject) => {
this.db.list("/profile/user_id").subscribe(result =>{
resolve(result));
}
});
}
Your constructor method should get the result like this
constructor(
public navCtrl: NavController,
public navParams : NavParams,
public modalCtrl:ModalController,
private afAuth:AngularFireAuth,
private afDatabase:AngularFireDatabase ,
public fb:FirebaseService) {
this.getDefaults();
this.selectedExercise=[];
console.log("home");
//get your result like this
this.fb.getShoppingItems().then(result =>{
this.exercises = result;
console.log(this.exercises);
});
}
use the async pipe:
*ngFor="let item of exercises | async"
with
exercises = this.afd.list('...');
I am building my first real MVC4 application and I have run into following issue.
I have a model for "User" class. Data for it are obtained through asynchronous call to webservice:
public sealed class AdminDMSEntities
{
public List<User> UserList { get; private set; }
public AdminDMSEntities()
{
this.UserList = new List<User>(0);
ServiceClient client = new ServiceClient();
client.GetUsersCompleted += (s, e) =>
{
if (e.Result == null)
throw new ArgumentNullException("No users were retrieved");
UserList = new List<User>(0);
e.Result.ForEach(w => this.UserList.Add(new User(w.Guid, w.TrusteeType, w.Username, w.Email, w.LastLogin, w.PasswordChanged, w.IsUsingTempPassword)));
};
client.GetUsersAsync();
}
}
I intend to use this class as I would use class derived from DbContext (if I could use Entity Framework which I cant). So far I have only users in the class.
I am using tis class in UsersController like this:
public class UsersController : Controller
{
private AdminDMSEntities adminEntities = new AdminDMSEntities();
//
// GET: /User/
public ActionResult Index()
{
return View(adminEntities.UserList);
}
}
The problem is that I will end up with InvalidOperationException, because controller is not waiting for async call completion and passes UserList to the view before it is properly filled with users.
I can have the call synchronous for the time being, but it is very likely I will be forced to use asynchronous calls later, so I would like to know how to ensure, that controller will wait for async call to be completed before UserList is passed to view...
Thanks in advance
EDIT: I have tried the approach with AsyncController as listed below, currently I have added this to AdminDMS entities class:
public static async Task<AdminDMSEntities> AdminDMSEntitiesAsync()
{
AdminDMSEntities result = null;
Task<AdminDMSEntities> getUsersAsyncTask = Task.Factory.StartNew(() => new AdminDMSEntities());
await getUsersAsyncTask;
return result;
}
and this is the change to the controller:
public class UsersController : AsyncController
{
private AdminDMSEntities adminEntities = null;
//
// GET: /User/
public async Task<ActionResult> Index()
{
if (adminEntities == null)
{
adminEntities = await AdminDMSEntities.AdminDMSEntitiesAsync();
}
return View(adminEntities.UserList);
}
}
The result is that adminEntities are containing an instance of the class, but there are no users in the list (there should be 11).
EDIT2: Since i was told that creating new task is not the right thing to do, I went with the first suggested approach removin AdminDMSEntities class from the code. My thanks to Darin for helping me out :)
You could use an asynchronous controller. The idea is to have your controller derive from the AsyncController class instead of the Controller class. This class provides methods that allow you to perform asynchronous operations.
For example:
public class MyController: AsyncController
{
public void IndexAsync()
{
AsyncManager.OutstandingOperations.Increment();
var client = new SomeClient();
client.GetUsersCompleted += (s, e) =>
{
UserList = new List<User>();
AsyncManager.Parameters["users"] = e.Result.Select(
w => new User(
w.Guid,
w.TrusteeType,
w.Username,
w.Email,
w.LastLogin,
w.PasswordChanged,
w.IsUsingTempPassword
)
)
.ToList();
AsyncManager.OutstandingOperations.Decrement();
};
client.GetUsersAsync();
}
public ActionResult IndexCompleted(IEnumerable<User> users)
{
return View(users);
}
}
and if you are using .NET 4.5 you could even take advantage of the new async keyword simplifying the asynchronous code even further. This is possible if you refactor your data access layer to the new pattern (i.e. return Tasks):
public class MyController: AsyncController
{
public async Task<ActionResult> Index()
{
var client = new SomeClient();
var users = await client.GetUsersAsync().Select(
w => new User(
w.Guid,
w.TrusteeType,
w.Username,
w.Email,
w.LastLogin,
w.PasswordChanged,
w.IsUsingTempPassword
)
)
.ToList();
return View(users);
}
}