I have a repository class which return Future by calling another function from another class which returns same Future. And I was trying to mock the repository class using the mockito when().thenAnswer() function like this
when(mockAccountRepository.authenticateWithEmail(
email: email,
password: password,
)).thenAnswer((_) => Future.value(SignedIn(token: token)));
the repository class has this function that returns the Future
Future<AccountEvent> authenticateWithEmail({
#required String email,
#required String password,
}) {
return _accountApiProvider.authenticateWithEmail(
email: email,
password: password,
);
}
the accountApiProvider class have the bellow function which also returns same Future
Future<AccountEvent> authenticateWithPhone(
{#required String phone, #required String password}) async {
final String data = '''{"phone":"$phone","password":"$password"}''';
try {
final Response response = await _dio.post('url', data: data);
if (response.statusCode == 200) {
print(response.data);
final String token = response.data.accessToken;
return SignedIn(token: token);
} else {
return SignInError('Internal Server Error');
}
} on DioError catch (e) {
return SignInError(e.message);
}
}
So, when I run the test it doesn't return the exact mock value provided in the thenAnswer() function.
Related
I'm trying to create a jest test for the below method. And I got errors for two scenarios.
So basically in checkKioskUserPhone method,
Find the user by the phone number( commonService.findKioskUserByPhone)
In findKioskUserByPhone method, we are gonna find the user by the phone number and send error messages if it's unregistered or already registered.
And then return user.
(back to checkKioskUserPhone) if the user doesn't have auth code and pin number we are gonna send him/her auth code and return jwt, and etc.
async checkKioskUserPhone(kioskLoginDto: KioskLoginDto): Promise<ResponseDto<UserAuthDto>> {
const user = await this.commonService.findKioskUserByPhone(kioskLoginDto);
const isConfirmedAuthCode = user.authCode === 'OK' ? true : false;
const isSetPin = user.pin ? true : false;
if (!isConfirmedAuthCode && !isSetPin) {
await this.userService.authenticatePhone(user.id, Builder(AuthorizePhoneDto).phone(user.phone).build());
}
const jwtInfo = await this.createToken(this.removeCredentialField(user));
return Builder<ResponseDto<UserAuthDto>>(ResponseDto)
.result(Builder(UserAuthDto).isConfirmedAuthCode(isConfirmedAuthCode).isSetPin(isSetPin).jwtInfo(jwtInfo).build())
.build();
}
async findKioskUserByPhone(kioskLoginDto: KioskLoginDto): Promise<User> {
const user = await this.userService.findOne({ where: { phone: kioskLoginDto.phone } });
// throw Error message when unregistered phone attempt to login
if (!user) {
throw new NotFoundException('User not found');
}
// throw Error message when registered phone by whatsapp attempt to login
if (user.provider !== Provider.KIOSK) {
throw new ConflictException('You are already joined by Whatsapp.');
}
return user;
}
Jest code
it('when unregistered phone attempt to login', async () => {
const phone = '2212223333';
const kioskLoginDto = Builder(KioskLoginDto).phone(phone).build();
service.commonService.findKioskUserByPhone = jest.fn().mockResolvedValue(null);
try {
await service.checkKioskUserPhone(kioskLoginDto);
expect('here').not.toBe('here');
} catch (error) {
expect(error).toBeInstanceOf(NotFoundException);
expect(error.message).toContain('User not found');
}
});
it('When registered phone by app attempt to login', async () => {
const phone = '2212223333';
const kioskLoginDto = Builder(KioskLoginDto).phone(phone).build();
const user = Builder(User).phone(phone).provider(Provider.WHATSAPP).build();
service.commonService.findKioskUserByPhone = jest.fn().mockResolvedValue(user);
try {
await service.checkKioskUserPhone(kioskLoginDto);
expect('here').not.toBe('here');
} catch (error) {
expect(error).toBeInstanceOf(ConflictException);
expect(error.message).toContain('You are already joined by Whatsapp.');
}
});
Jest Error screenshot
you're overriding the findKioskUserByPhone method to just return null:
service.commonService.findKioskUserByPhone = jest.fn().mockResolvedValue(null);
so findKioskUserByPhone simply is never running, a mock function is just returning null, and is thus never throwing the error you expect. instead, here:
const user = await this.commonService.findKioskUserByPhone(kioskLoginDto);
user is getting set to null and here:
const isConfirmedAuthCode = user.authCode === 'OK' ? true : false;
you're trying access some authCode property of null, which throws the TypeError you're getting.
you probably meant to override the findOne method on the user service:
service.userService.findOne = jest.fn().mockResolvedValue(null);
so the error you want will actually throw in findKioskUserByPhone
(note I don't know if this is actually where you have the user service to provide the mock, I'm just assuming)
i am trying to validate my user resister form in adonis js by using
import {schema, rules} from '#ioc:Adonis/Core/Validator';
after register I am getting error of
{"errors":[{"rule":"required","field":"username","message":"required validation failed"},{"rule":"required","field":"username","message":"required validation failed"},{"rule":"required","field":"password","message":"required validation failed"},{"rule":"required","field":"phone","message":"required validation failed"}]}
validation code is
import { HttpContextContract } from '#ioc:Adonis/Core/HttpContext';
import {schema, rules} from '#ioc:Adonis/Core/Validator';
import user from 'App/Models/User';
export default class LoginController{
public async loginShow({view}:HttpContextContract){
return view.render('backend/login');
}
public async register({request,response,auth}:HttpContextContract){
const userSchema = schema.create({
username: schema.string({trim:true},[rules.required(), rules.unique({table:'users', column:'username', caseInsensitive: true})]),
email: schema.string({trim:true},[rules.email(), rules.required(), rules.unique({table:'users', column:'email', caseInsensitive: true})]),
password: schema.string({},[rules.minLength(6), rules.maxLength(20)]),
phone: schema.string({},[rules.unique({table:'users', column:'phone'})])
})
const data = await request.validate({schema: userSchema})
const User = await user.create(data)
await auth.login(User)
return response.redirect('/')
}
public async registerShow({view}:HttpContextContract){
return view.render('backend/register');
}
public async login({request,response,auth}:HttpContextContract){
const {uid, password} = request.only(['uid','password'])
try{
await auth.attempt(uid,password)
} catch (error){
console.log('form','your email or password is incorrect')
return response.redirect().back()
}
return response.redirect('/')
}
}
migration table is given below:
import BaseSchema from '#ioc:Adonis/Lucid/Schema'
export default class Users extends BaseSchema {
protected tableName = 'users'
public async up () {
this.schema.createTable(this.tableName, (table) => {
table.increments('id')
table.string('name',50).nullable()
table.string('username',50).unique().notNullable()
table.string('email',100).unique().notNullable()
table.string('phone',18).unique().nullable()
table.string('password',50).notNullable()
table.string('country',30).nullable()
table.string('city',40).nullable()
table.string('address_1',40).nullable()
table.string('address_2',40).nullable()
table.string('token',40).nullable()
table.dateTime('token_expiry')
table.boolean('is_active').defaultTo(false)
table.timestamps(true,true)
})
}
public async down () {
this.schema.dropTable(this.tableName)
}
}
How can I persist user session between user usages of the application (closed browser/tab without logout)?
Can a Blazor WebAssembly app persist the user session in a cookie with some expiration date?
Right now I got this startup configuration, and everything related to authorization with IdentityServer works fine. The tokens in Identity Server have a 30 days expiration period.
// startup.cs
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("Local", options.ProviderOptions);
options.UserOptions.RoleClaim = "RoleName";
});
// appsettings.json
"Local": {
"Authority": "https://idserver.url",
"ClientId": "Client",
"DefaultScopes": [
"openid",
"profile",
"email",
"roles",
"offline_access"
],
"ResponseType": "code",
"PostLogoutRedirectUri": "https://localhost:5004/authentication/logout-callback",
"RedirectUri": "https://localhost:5004/authentication/login-callback"
}
Is there a way to persist user session in cookies?
I will try my best to answer your question, because I had the same issue and figured it out.
So essentially in between sessions, the cookie is still there saved in the browser, and even though your AuthenticationStateProvider is not set to authenticated, if you try and execute a call against your API, the Cookie Handler will include the cookie in the request and it will authenticate.
So I was able to implement a solution based off an article that I found here:
https://www.learmoreseekmore.com/2022/04/blazorwasm-cookie-series-part-1-blazor-webassembly-cookie-authentication.html
I assume that you have a delegation handler that attaches the cookie to outgoing HTTP requests like so:
public class CookieHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
return await base.SendAsync(request, cancellationToken);
}
}
This guy will continue to attach that cookie in between sessions.
The problem is that the AuthenticationStateProvider will not persist his state. So what I did was save a local variable into the local browser storage that allows me to remember if I am authenticated. When I check if I am logged in, I make the following checks:
I check if the AutenticationStateProvider is authenticated.
If not, I check if I have set a local variable in the local storage indicating if I am authenticated. If that local variable exists, then he will make an API call to my web service asking for my user information. If that call completes successfully, then I update my AuthenticationStateProvider
I have a dependency injection service I call ILoginService that has an implementation that looks a bit like so:
ILocalStorageService _storageService;
AuthenticationStateProvider _authStateProvider;
public UserModel User { get; private set; } = new UserModel();
public LoginService(ILocalStorageService storageService, IHttpClientFactory clientFactory, AuthenticationStateProvider authStateProvider) : base(clientFactory)
{
_authStateProvider = authStateProvider;
_storageService = storageService;
}
public async Task<bool> IsLoggedIn()
{
var authState = await _authStateProvider.GetAuthenticationStateAsync();
if (authState.User?.Identity?.IsAuthenticated == true)
{
return true;
}
var isauthenticated = await _storageService.GetItemAsync<string>("isauthenticated");
if (!string.IsNullOrWhiteSpace(isauthenticated))
{
using (var client = _clientFactory.CreateClient("API"))
{
var response = await client.GetAsync("/login");
if (response.IsSuccessStatusCode)
{
string jsonStr = await response.Content.ReadAsStringAsync();
UserModel? user = JsonConvert.DeserializeObject<UserModel>(jsonStr);
if (user == null)
{
await _storageService.RemoveItemAsync("isauthenticated");
return false;
}
else
{
(_authStateProvider as CustomAuthStateProvider)?.SetAuthInfo(user);
this.User = user;
return true;
}
}
}
}
await _storageService.RemoveItemAsync("isauthenticated");
return false;
}
public async Task<bool> Login(LoginCredentials credentials)
{
try
{
if (credentials == null) throw new ArgumentNullException(nameof(credentials));
using (var client = _clientFactory.CreateClient("API"))
{
StringContent body = new StringContent(JsonConvert.SerializeObject(credentials), System.Text.Encoding.UTF8, "application/json");
var response = await client.PostAsync("/login", body);
if (response.IsSuccessStatusCode)
{
string jsonStr = await response.Content.ReadAsStringAsync();
UserModel? user = JsonConvert.DeserializeObject<UserModel>(jsonStr);
if (user == null)
{
await _storageService.RemoveItemAsync("isauthenticated");
return false;
}
else
{
(_authStateProvider as CustomAuthStateProvider)?.SetAuthInfo(user);
this.User = user;
await _storageService.SetItemAsync<string>("isauthenticated", "true");
return true;
}
}
else
{
#if DEBUG
string responseStr = await response.Content.ReadAsStringAsync();
#endif
await _storageService.RemoveItemAsync("isauthenticated");
return false;
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
await _storageService.RemoveItemAsync("isauthenticated");
return false;
}
}
public async Task<bool> Logout()
{
try
{
using (var client = _clientFactory.CreateClient("API"))
{
var response = await client.DeleteAsync("/login");
if (!response.IsSuccessStatusCode)
{
#if DEBUG
string responseStr = await response.Content.ReadAsStringAsync();
#endif
}
}
(_authStateProvider as CustomAuthStateProvider)?.ClearAuthInfo();
await _storageService.RemoveItemAsync("isauthenticated");
return true;
}
catch (Exception ex)
{
Console.WriteLine(ex);
return false;
}
}
I hope this helps you fix your problem and anybody else that comes along.
I am writing unit test for my auth.service module validateReader unit,
async validateReader(username: string, password: string): Promise<any> {
const reader = await this.readerService.findOne(username);
const match = await bcrypt.compare(password, reader.password);
if (match) {
const { password, ...result } = reader.toJSON();
this.logger.info(
`Reader ${reader.username} username & password validation passed`,
);
return result;
}
this.logger.warn(`Incorrect password in reader ${reader.username} login`);
return null;
}
I tried to mock readerService.findOne function as following:
jest
.spyOn(readerService, 'findOne')
.mockImplementationOnce(() => Promise.resolve(readerStub()));
but did not work, always got error - Cannot spy the findOne property because it is not a function; I think the reason is the returned value must be mongoose document object (need toJSON() method), but my readerStub() just return a reader object, missing lots of document properties. Is there anyway I can set up stub for document & reader? And maybe my analysis is wrong, there is other reason to got this error.
Following is my mock readerService:
export const ReaderService = jest.fn().mockReturnValue({
register: jest.fn().mockResolvedValue(readerStub()),
findOne: jest.fn().mockResolvedValue(readerStub()),
getProfile: jest.fn().mockResolvedValue(readerStub()),
updateProfile: jest.fn().mockResolvedValue(readerStub()._id),
changePwd: jest.fn().mockResolvedValue(readerStub().username),
login: jest.fn().mockResolvedValue(accessTokenStub()),
tokenRefresh: jest.fn().mockReturnValue(accessTokenStub()),
logout: jest.fn().mockResolvedValue(readerStub()._id),
});
I developing an application an flutter and use clean architecture.
I created a use case return a List from a stream. The stream sends the List from an observer. Above is the code:
abstract class GetAllServicesObserver implements Observer {
void onGetAllSuccess(List<Service> services);
void onGetAllError(Exception error);
}
class GetAllServices extends UseCase<GetAllServicesObserver, NoParams> {
final User _user;
final ServiceRepository _serviceRepository;
StreamSubscription _subscription;
GetAllServices({
#required User user,
#required ServiceRepository serviceRepository,
}) : _user = user,
_serviceRepository = serviceRepository;
#override
action(observer, params) async {
_subscription?.cancel();
final _stream = _serviceRepository.all(_user);
_subscription = _stream.listen((services) {
observer.onGetAllSuccess(services);
}, onError: (e) {
observer.onGetAllError(e);
});
}
}
And I created an unit test to this use case:
test('should to return all services', () {
//setup
when(repository.all(user)).thenAnswer((_) async* {
yield List<Service>();
});
final useCase = GetAllServices(user: user, serviceRepository: repository);
useCase.observer = observer;
//run
useCase();
//verify
verify(observer.onGetAllSuccess(List<Service>()));
});
}
But it's returns the follow message and not pass:
ERROR: No matching calls (actually, no calls at all).
(If you called verify(...).called(0);, please instead use verifyNever(...);.)
Would anyone know what the problem is?
Have you tried untilCalled before verify? e.g.:
await untilCalled(some method that will be called)