Factory tries to create two objects with same properties - javascript

I'm writing tests for an express app that implements a user CRUD. Before writing integration tests, I made a factory that would create users for the tests:
factories.js
import faker from 'faker';
import { factory } from 'factory-girl';
import User from '../../src/app/models/User';
factory.define('User', User, {
name: faker.name.firstName(),
email: faker.internet.email(),
password: faker.internet.password(),
admin: false,
});
export default factory;
Nice. Now whenever I needed to create a user for tests, I would just have to use factory.create(), right? Wrong. In one of the test suites I need two users, one that is admin and other that isn't. So I wrote this setup/teardown:
let user, admin;
const createUsers = async () => {
// Runs ok
user = await factory.create('User');
// Error
admin = await factory.create('User', { admin: true });
};
const removeUsers = async () => {
await user.remove();
await admin.remove();
};
beforeAll(async () => {
await createUsers();
});
afterAll(async () => {
await removeUsers();
mongoose.connection.close();
});
user = await factory.create(...) runs fine, but admin = await factory.create(...) raises a MongoError: E11000 duplicate key error collection.
This is because the factory tries to create a user with an email that is already in use (email is unique for the model User). I could ensure that wouldn't happen by passing a specific email to the create method in the same way I passed admin: true, but that wouldn't make much sense since I created the factory to avoid this kind of trouble.
Can you guys point out what am I doing wrong here? I guess it has something to do with the way I defined my factory. Thanks in advance.
EDIT: As suggested, I fixed it by using factory.sequence.
factory.define('User', User, {
name: factory.sequence('User.name', () => faker.name.firstName()),
lastName: factory.sequence('User.lastName', () => faker.name.lastName()),
email: factory.sequence('User.email', () => faker.internet.email()),
password: factory.sequence('User.password', () => faker.internet.password()),
redefinePassword: false,
admin: false,
});

While faker.internet.email() will create a new fake email every time it is called, you only call it once when defining your template object for the factory. Look into the factory.sequence API for a way to make the factory run some code for each object being created https://www.npmjs.com/package/factory-girl#defining-factories
Or simply pass the function faker.internet.email, without the () and I think factory-girl will call that function each time as well, you can also make your define call take a function that returns this object (after calling faker.internet.email()), so many options!

Related

Google Firebase authentication in ReactNative App

I was developing an app which I like implements Firebase as Authenticating system.
My problem comes when I try to set up the Authentication with Google provider when I try to modify the colletion of firestore where the users are saved. My code is the following:
export const loginWithGoogle = () => {
const navigation = useNavigation();
useEffect(() => {
setTimeout(() => {
navigation.navigate('/RegisterScreen');
}, 10000);
}, []);
return () => {
return firebase
.auth()
.signInWithPopup(Providers.google)
.then(async result => {
//console.log(result.credential.accessToken);
const user = result.user;
console.log(user);
//This 2 lines below doesn't work to get the colletion.
db.('users').setItem('userid', user!.uid);
collection.(db,'users').setItem('photoURL', user!.photoURL);
//TODO if userid exists IN USERS db then use update IF NULL use set
await db.collection('users').doc(user!.uid).update({
// id: user.uid,
name: user!.displayName,
email: user!.email,
phone: user!.phoneNumber,
photoURL: user!.photoURL,
});
})
.then(() => {
navigation.navigate('ProtectedScreen');
})
.catch(err => {
console.log(err);
});
};
};
So I guess that my error comes from unknowledge of how to manage data saved on firestore.
If you can help take thanks in advance !
There are some thing we need to clear here:
You can just merge the data. There is no need to read/get it from Firestore to check if it is there and save it onyl if it's not. You will be charged for reads and writes. In the end it's cheaper to always just write without checking if something exists.
Also this code here:
db.('users').setItem('userid', user!.uid);
collection.(db,'users').setItem('photoURL', user!.photoURL);
especially with the db.( and collection.( doens't look good. Even if it is it's not for getting data but for saving it.
Could you pls clarify witch Firebase SDK you use: version 8 or 9. Also pls check a little bit the docs here.

Update Profile During SignUp Angular Firebase

I am using angularfire/firebase for an Angular project where I am working through authentication.
When a user signs up, the following function is called:
signup(email: string, password: string, name: string) {
return this.fireAuth
.createUserWithEmailAndPassword(email, password)
.then((res) => {
res.user?.updateProfile({
displayName: name,
});
// set vars and local storage
});
}
In my app.component, I have the following subscription to track changes and store in my state management (I don't think StateMgmt is the issue here):
ngOnInit() {
this.fireAuth.authState.user.subscribe((res) => {
if(res) {
console.log(res);
console.log(res.displayName);
this.store.dispatch(new SetUser(res));
}
}
When a user signs up, the name they enter upon signup is set through the res.user.updateProfile(... function, and since the authState had a change, the subscription in the app.component prints out an object and stores to the SetUser(res) state.
The console.log() from here in the app state prints out a large object, including the email, uid, and displayName. This is then stored in my state object. However, on theconsole.log(res.displayName), the result is null until I refresh the page triggering an "auto logon" where the displayName is fetched from firebase.
It seems to me the object of the .then((res)... from my signUp() updateProfile is not being triggered until after the user is already changed and stored.
Is there a way to make this signUp function work so that the SetUser(res) is only dispatched once the updateProfile({... is complete and the displayName is no longer null?
From my app.component console.log(res):
{
"uid": "vbn6TA8vTiLi7J2",
"displayName": "my name",
"photoURL": null,
"email": "email#email.com",
...
}
app.component console.log(res.displayName):
null
EDIT
Another interesting thing I found is that when I take a copy of the res and print it out, the displayName is also null.
const r = JSON.parse(JSON.stringify(res));
console.log(res);
console.log(r);
the console.log(r) has an object where the displayName is null. The console.log(res) has an object where the displayName is NOT null and is the name passed in to the signUp(... function.
This maybe a race condition within Angular Fire, I would ensure that the user is finalized before updating the user.
async UpdateProfile(displayName: string) {
const profile = {
displayName: stored.username,
photoURL: stored.profile,
email: stored.email,
}
return (await this.afAuth.currentUser).updateProfile(profile);
}
I am making the assumption you are storing the user details via a form object as stored but this can easily be from the auth provider or a custom object
Even waiting for the user to be finalized via async await before moving on did not work.
What I ended up doing was the following:
In my signUp() function, once the user was created, I retrieved the current use via this.fireAuth.authState (fireAuth is from AngularFireAuth in my constructor). I subscribed to this, while piping to only take the first value, and then dispatched my SetUser function. This ensures that the function is only running after the updateProfile is complete using a .then().
Because I moved the state dispatch into my signup function, I removed the subscription from my AppComponent.
Here is my signup function from the auth service:
...
constructor(
public fireAuth: AngularFireAuth,
) {
this.user = this.fireAuth.authState;
}
signup(email: string, password: string, name: string) {
return this.fireAuth
.createUserWithEmailAndPassword(email, password)
.then((res) => {
res.user
?.updateProfile({
displayName: name,
})
.then(() => {
this.user.pipe(first()).subscribe((res) => {
this.store.dispatch(new SetUser(res));
});
});
});
}

Mocking node_modules which return a function with Jest?

I am writing a typeScript program which hits an external API. In the process of writing tests for this program, I have been unable to correctly mock-out the dependency on the external API in a way that allows me to inspect the values passed to the API itself.
A simplified version of my code that hits the API is as follows:
const api = require("api-name")();
export class DataManager {
setup_api = async () => {
const email = "email#website.ext";
const password = "password";
try {
return api.login(email, password);
} catch (err) {
throw new Error("Failure to log in: " + err);
}
};
My test logic is as follows:
jest.mock("api-name", () => () => {
return {
login: jest.fn().mockImplementation(() => {
return "200 - OK. Log in successful.";
}),
};
});
import { DataManager } from "../../core/dataManager";
const api = require("api-name")();
describe("DataManager.setup_api", () => {
it("should login to API with correct parameters", async () => {
//Arrange
let manager: DataManager = new DataManager();
//Act
const result = await manager.setup_api();
//Assert
expect(result).toEqual("200 - OK. Log in successful.");
expect(api.login).toHaveBeenCalledTimes(1);
});
});
What I find perplexing is that the test assertion which fails is only expect(api.login).toHaveBeenCalledTimes(1). Which means the API is being mocked, but I don't have access to the original mock. I think this is because the opening line of my test logic is replacing login with a NEW jest.fn() when called. Whether or not that's true, I don't know how to prevent it or to get access to the mock function-which I want to do because I am more concerned with the function being called with the correct values than it returning something specific.
I think my difficulty in mocking this library has to do with the way it's imported: const api = require("api-name")(); where I have to include an opening and closing parenthesis after the require statement. But I don't entirely know what that means, or what the implications of it are re:testing.
I came across an answer in this issue thread for ts-jest. Apparently, ts-jest does NOT "hoist" variables which follow the naming pattern mock*, as regular jest does. As a result, when you try to instantiate a named mock variable before using the factory parameter for jest.mock(), you get an error that you cannot access the mock variable before initialization.
Per the previously mentioned thread, the jest.doMock() method works in the same way as jest.mock(), save for the fact that it is not "hoisted" to the top of the file. Thus, you can create variables prior to mocking out the library.
Thus, a working solution is as follows:
const mockLogin = jest.fn().mockImplementation(() => {
return "Mock Login Method Called";
});
jest.doMock("api-name", () => () => {
return {
login: mockLogin,
};
});
import { DataManager } from "../../core/dataManager";
describe("DataManager.setup_api", () => {
it("should login to API with correct parameters", async () => {
//Arrange
let manager: DataManager = new DataManager();
//Act
const result = await manager.setup_api();
//Assert
expect(result).toEqual("Mock Login Method Called");
expect(mockLogin).toHaveBeenCalledWith("email#website.ext", "password");
});
});
Again, this is really only relevant when using ts-jest, as using babel to transform your jest typescript tests WILL support the correct hoisting behavior. This is subject to change in the future, with updates to ts-jest, but the jest.doMock() workaround seems good enough for the time being.

How to test if a document's property changed?

I'm writing tests for an Express CRUD app. The app has an account recovery route that accepts POST requests with an email in the body. It searches for an user with a given email, changes it's password then sends an email with the new password and returns 200.
So I want to write a test that checks if the user's password has indeed changed.
This was my attempt:
First I created a user factory using the factory-girl lib.
// factory.js file
const { factory } = require('factory-girl');
const User = require('../../models/User');
factory.define('User', User, {
email: factory.sequence('User.email', n => `fakeuser${n}#mail.com`),
name: factory.chance('name'),
password: factory.chance('word')
});
module.exports = factory;
Nice. Then I wrote the test:
// One test inside my test suite
it('should return 200 and change user\'s password', async () => {
const user = await factory.create('User');
const oldPassword = user.password;
const response = await request(app)
.post(endpoint)
.send({ email: user.email });
const passwordChanged = oldPassword !== user.password;
expect(response.status).toBe(200);
expect(passwordChanged).toBe(true);
await user.remove();
});
But passwordChanged is always being evaluated to false, since user.password doesn't change at all. I mean, on the database, it changes (the method works correctly), but on my test the user object is not updated after the request.
In other words, what I want to test here is the state of a document in the database, not only what the request is returning. Any suggestions on how to achieve this?
You can open a change stream and listen for when your fields are changed, then run the comparison right then.

How Testcafe handle login errors?

I'm testing a website that needs authentication with different users. It's working most of the time but sometimes, login fails and Testcafé doesn't detect it before running into the actual test code. Rather than raising an error into the login method, it fails when finding a DOM element in the test page. So it keeps the wrong login information and other tests with the same user will fail too.
I know the way to detect a login error on my website but I can't say to Testcafé:
"Hey! Something wrong appends when login, don't save login information for this user and try again in next tests"
EDIT:
Rather than using hardcoded login information, I use a separate file logins.ts with the following structure and I adapt it to add loggedIn and role fields :
adminUserCredentials: { login: 'mylogin', pwd: 'mypass', role: null, loggedIn: false }
Then I use it as follow:
function createUserForSpecificEnv(user: User, baseUrl: string): Role {
if(!user.loggedIn) {
user.role = Role(baseUrl, async t => {
await t
.wait(1000)
.typeText('#loginInput', user.login)
.typeText('#passwordInput', user.pwd)
.click('#Btn')
if(await Selector('#user-info').visible) {
user.loggedIn = true
}
})
}
return user.role
}
const adminUserRole = getRole(adminUserCredentials)
test("test 1", async t => {
t.useRole(adminUserRole)
}) // The test go on the login page, auth failed (expected) and can't find #user-info (expected)
test("test 2", async t => {
t.useRole(adminUserRole)
}) // The test doesn't go to the login page and directly says : can't find #user-info
But it's still not working... TestCafe tries to log in on the first test and then it directly reuses the same login information.
Any help would be appreciated! Thank you :-)
EDIT 2
I clarify the fact that I use variable to store the role (see comments)
If login fail is expected I'd suggest not using Role for this particular test. You can extract an authentication logiс into a separate function and use it in this test directly and in the Role constructor:
const logIn = async t => {
//login steps
};
user.role = Role(baseUrl, logIn);
test("test 1", async t => {
await logIn(t);
}); // The test go on the login page, auth failed (expected) and can't find #user-info (expected)
test("test 2", async t => {
t.useRole(adminUserRole)
});
I think you can check some Selector on the last step of Role initialization to make sure that you are correctly logged in. If you are not, you need to recreate your role for further usage in the next tests. Please see the following code, which demonstrates my idea:
import { Role, Selector } from 'testcafe';
let role = null;
let loggedIn = false;
function getRole() {
if (!loggedIn) {
role = new Role('http://example.com', async t => {
console.log('role initialize');
// await t.typeText('#login', 'login');
// await t.typeText('#password', 'password');
// await t.click('#signin');
// NOTE: ensure that we are actually logged in
if(await Selector('#user_profile').exists)
loggedIn = true;
});
}
return role;
}
fixture `fixture`
.page `../pages/index.html`;
test(`test1`, async t => {
await t.useRole(getRole());
});
test(`test2`, async t => {
await t.useRole(getRole());
});
test(`test3`, async t => {
await t.useRole(getRole());
});

Categories

Resources