I'm creating a text based adventure game to practice testing, I'm using Jest and I have really enjoyed it so far.
I am now testing a function that decreases an objects value by 1 (player.health), when I created the test it does passed successfully and the health does decrease but I get...
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with --detectOpenHandles to troubleshoot this issue.
I'm not awaiting any promises, so not sure why I'm getting this message, I tried to run --detectOpenHandles but this doesn't seem to print anything. Why am I getting this message?
This may be unrelated but should I be mocking the healthDecrease function? It was my understanding that because I have control I do not need to mock.
index.js - where the player object is and order of the game will be executed
const { name } = require('./functions/inq')
let player = {
name: "",
weapon: "",
health: 10
}
const start = async () => {
console.log('what name?')
player.name = await name()
console.log('hello', player.name)
}
start()
module.exports = {
player
}
functions.js - where the basic functions for the game are stored
const { player } = require('../index')
const healthDecrease = () => {
return player.health--
}
module.exports = {
healthDecrease
}
functions.test.js - test file
const { healthDecrease } = require('../functions/functions')
const { player } = require('../index')
describe('decrease user health', () => {
test('health decreases by 1', () => {
healthDecrease()
expect(player.health).toBe(10) //will fail
})
test('health decreases by 2', () => {
healthDecrease()
healthDecrease()
expect(player.health).toBe(10) //will fail
})
})
Related
This is the code where this is happening:
export const initializeSpotifySDK = async (token: string, trackId: string, contextUri: string, playbackStateChangeHandler: (state) => void, errorHandler: (message: string) => void): Promise<SpotifyPlayer> => {
embedSpotifyScript();
return new Promise(resolve => {
window.onSpotifyWebPlaybackSDKReady = () => {
try {
// #ts-ignore
const player = new Spotify.Player({
name: 'Mira',
getOAuthToken: callback => { callback(token); }
});
// Error handling - pass an error handler!!!
player.addListener('initialization_error', ({ message }) => {
errorHandler(message);
});
player.addListener('authentication_error', ({ message }) => {
errorHandler(message);
});
player.addListener('account_error', ({ message }) => {
errorHandler(message);
});
player.addListener('playback_error', ({ message }) => {
errorHandler(message);
});
// Playback state handler - pass a handler as well!!!
player.addListener('player_state_changed', state => { playbackStateChangeHandler(state); });
player.addListener('ready', ({ device_id }) => {
const spotifyPlayer = new SpotifyPlayer(player, device_id, trackId, contextUri, token, true);
resolve(spotifyPlayer);
});
player.addListener('not_ready', ({ device_id }) => {
const spotifyPlayer = new SpotifyPlayer(player, device_id, trackId, contextUri, token, false);
resolve(spotifyPlayer);
});
player.connect();
} catch (err) {
logError(err);
resolve(new SpotifyPlayer(null, '', '', token, '', false));
}
};
});
};
And this is the test:
it('should initialize the Spotify SDK and return a SpotifyPlayer object', async () => {
const token = 'abc123';
const trackId = '123';
const contextUri = 'spotify:album:456';
const playbackStateChangeHandler = jest.fn();
const errorHandler = jest.fn();
const spotifyPlayer = await initializeSpotifySDK(
token,
trackId,
contextUri,
playbackStateChangeHandler,
errorHandler
);
console.log({ spotifyPlayer });
expect(spotifyPlayer).toBeInstanceOf(SpotifyPlayer);
expect(spotifyPlayer.deviceId).toBeDefined();
expect(spotifyPlayer.trackId).toEqual(trackId);
expect(spotifyPlayer.contextUri).toEqual(contextUri);
expect(spotifyPlayer.token).toEqual(token);
});
The whole error
✕ should initialize the Spotify SDK and return a SpotifyPlayer object (5003 ms)
● spotifySdkService › should initialize the Spotify SDK and return a SpotifyPlayer object
thrown: "Exceeded timeout of 5000 ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
108 | });
109 |
> 110 | it('should initialize the Spotify SDK and return a SpotifyPlayer object', async () => {
| ^
111 | const token = 'abc123';
112 | const trackId = '123';
113 | const contextUri = 'spotify:album:456';
at services/spotifySdkService.spec.ts:110:3
at Object.<anonymous> (services/spotifySdkService.spec.ts:17:1)
at TestScheduler.scheduleTests (../node_modules/#jest/core/build/TestScheduler.js:333:13)
at runJest (../node_modules/#jest/core/build/runJest.js:404:19)
at _run10000 (../node_modules/#jest/core/build/cli/index.js:320:7)
at runCLI (../node_modules/#jest/core/build/cli/index.js:173:3)
Any ideas on what I am doing wrong?
The error message is telling you exactly what's going wrong: the code you are testing is taking more than 5 seconds to complete, which isn't unexpected for something that is making a internet request.
I would first do as the error suggests to make sure that your test functions, even if very slowly:
jest.setTimeout(60000) // a whole minute!
If it fails, bump it to 5 minutes and try again (and wait).
If it works, and even if it doesn't, you can then try to isolate what exactly is taking so long (or not returning) by inserting timing measurements around code most likely taking a long time:
const start = Date.now()
console.log(`starting...`)
player.connect()
console.log(`elapsed millis: ${Date.now() - start}`)
If you see starting... but never the elapse millis even after a long wait, then the code in between the two console log statements appears to be hanging somewhere.
If you never even see starting... in the console, then either jest is suppressing console output or whatever is taking long is happening before the code you chose to measure.
Call your function from a plain script to take jest out of the picture. This will keep things simple and will uncover whether jest or how you're using it is the source of the problem (i doubt it0.
If you still don't see starting... then move the lines:
const start = Date.now()
console.log(`starting...`)
to the top of your function.
If you still don't see it, you are doing something else wrong.
If you see the elapsed time, and it is long, i.e. greater than 5000, narrow down which line it causing it by moving the timing statements. If it is a single function call to one of your functions, you can move the timing statements to the inside of that function and narrow the source in there.
I suspect player.connect() is what's taking so long, and that could be either normal or an issue with your internet connection, or perhaps your permissions/credential used in making the Spotify request.
I've been reading some articles, and posts here on Stack Overflow about when I should mock a function and when I shouldn't, but I have a case where I'm not sure about what to do.
I have a UserService class which uses dependency injection concept to receive dependencies through its constructor.
class UserService {
constructor(userRepository) {
this.userRepository = userRepository;
}
async getUserByEmail(userEmail) {
// would perform some validations to check if the value is an e-mail
const user = await this.userRepository.findByEmail(email);
return user;
}
async createUser(userData) {
const isEmailInUse = await this.getUserByEmail(userData.email);
if(isEmailInUse) {
return "error";
}
const user = await this.userRepository.create(userData);
return user;
}
}
I want to test if the createUser method works properly, and for my tests, I created a fake userRepository which is basically a object with mocked methods that I will use while instantiating UserService Class
const UserService = require('./UserService.js');
describe("User Service tests", () => {
let userService;
let userRepository;
beforeEach(() => {
userRepository = {
findOne: jest.fn(),
create: jest.fn(),
}
userService = new UserService(userRepository);
});
afterEach(() => {
resetAllMocks();
});
describe("createUser", () => {
it("should be able to create a new user", async () => {
const newUserData = { name: 'User', email: 'user#test.com.br' }
const user = { id: 1, name: 'User', email: 'user#test.com.br' }
userRepository.create.mockResolvedValue(user);
const result = await userService.createUser();
expect(result).toStrictEqual(user);
})
})
})
Note that in the createUser method, there is a call to the getUserByEmail method which is also a method of UserService class, and that is where I got confused.
Should I mock the getUserByEmail method even it is a method of the class I'm testing? If it is not the correct approach, what should I do?
You should almost always prefer not to mock parts of the thing you're supposed to be testing, in this case UserService. To illustrate why, consider these two tests:
Provides a test double implementation for findByEmail on the repo object:
it("throws an error if the user already exists", async () => {
const email = "foo#bar.baz";
const user = { email, name: "Foo Barrington" };
const service = new UserService({
findByEmail: (_email) => Promise.resolve(_email === email ? user : null),
});
await expect(service.createUser(user)).rejects.toThrow("User already exists");
});
Stubs out the service's own getUserByEmail method:
it("throws an error if the user already exists", async () => {
const email = "foo#bar.baz";
const user = { email, name: "Foo Barrington" };
const service = new UserService({});
service.getUserByEmail = (_email) => Promise.resolve(_email === email ? user : null);
await expect(service.createUser(user)).rejects.toThrow("User already exists");
});
For your current implementation, both pass just fine. But let's think about how things might change.
Imagine we need to enrich the user model getUserByEmail provides at some point:
async getUserByEmail(userEmail) {
const user = await this.userRepository.findByEmail(userEmail);
user.moreStuff = await.this.userRepository.getSomething(user.id);
return user;
}
Obviously we don't need this extra data just to know whether or not the user exists, so we factor out the basic user object retrieval:
async getUserByEmail(userEmail) {
const user = await this._getUser(userEmail);
user.moreStuff = await.this.userRepository.getSomething(user.id);
return user;
}
async createUser(userData) {
if (await this._getUser(userData.email)) {
throw new Error("User already exists");
}
return this.userRepository.create(userData);
}
async _getUser(userEmail) {
return this.userRepository.findByEmail(userEmail);
}
If we were using test 1, we wouldn't have to change it at all - we're still consuming findByEmail on the repo, the fact that the internal implementation has changed is opaque to our test. But with test 2, that's now failing even though the code still does the same things. This is a false negative; the functionality works but the test fails.
In fact you could have applied that refactor, extracting _getUser, prior to a new feature making the need so clear; the fact that createUser uses getUserByEmail directly reflects accidental duplication of this.userRepository.findByEmail(email) - they have different reasons to change.
Or imagine we make some change that breaks getUserByEmail. Let's simulate a problem with the enrichment, for example:
async getUserByEmail(userEmail) {
const user = await this.userRepository.findByEmail(userEmail);
throw new Error("lol whoops!");
return user;
}
If we're using test 1, our test for createUser fails too, but that's the correct outcome! The implementation is broken, a user cannot be created. With test 2 we have a false positive; the test passes but the functionality doesn't work.
In this case, you could say that it's better to see that only getUserByEmail is failing because that's where the problem is, but I'd contend that would be pretty confusing when you looked at the code: "createUser calls that method too, but the tests say it's fine...".
You shouldn't mock any of these functions since its creating users and reading data from the database. If you mock them then what's the point of the test. In other words, you wouldn't know if your app is working correctly with the database or not. Anyway, I would mock functions such as the functions that send emails and so on. Don't mock the functions that are the heart of the application. You should have a database for testing and another one for production.
I would love some help on this. I've been working on it for too long to deem not asking a question.
Thank you in advance for your time and attention,
I have scoured the internet and tried many things but I am still having no luck - here's the issue.
All in all - I am following along with
https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning
Using react-testing-library in tandem with jest - and running into two issues
Warning: An update to Login inside a test was not wrapped in
act(...).
Jest isn’t picking up that the mock is being called
I really think I have everything wired up correctly - unsure what I am missing.
Inside the mock I can put a console.log and can confirm that the mock is indeed being called.
/*
Test
*/
it('should call props.fetchSignIn with the username and password', async () => {
const promise = Promise.resolve({
email: 'test#circulate.social',
firstName: 'Mike',
lastName: 'A',
});
const fetchSignIn = jest.fn(() => promise);
const { queryByTestId, queryByPlaceholderText } = renderLogin({
fetchSignIn,
});
const emailInput = queryByPlaceholderText('joedoe#gmail.com');
const passwordInput = queryByPlaceholderText('password');
const submitButton = queryByTestId('submitButton');
fireEvent.change(emailInput, {
target: { value: 'mike#circulate.social' },
});
fireEvent.change(passwordInput, { target: { value: 'Password1!' } });
fireEvent.click(submitButton);
// I expected this to fail from the parameters being wrong
expect(fetchSignIn).toHaveBeenCalledWith('asf');
await act(() => promise);
});
/*
Functional Component using the `useState(...)` hooks
onFormFinished is the form `onSubmit` handler
*/
const handleSignIn = async (
email: string,
password: string
): Promise<UserContextType['user']> => {
setIsLoginInFlight(true);
try {
const result = await props.fetchSignIn(email, password);
setIsLoginInFlight(false);
return result;
} catch (error) {
// Other logic removed
};
const onFormFinish = async (values: FormValues): Promise<void> => {
const { email, password } = values;
const signInResult = await handleSignIn(email, password);
// Other logic removed
};
Two things are happening
1 - That warning
2 - Test is failing because fetchSignIn was never called
What is expected
The test to fail due to fetchSignIn being called, but the parameters being wrong
Any input or ask for clarification is welcome.
Thank you
Let's address your second problem first.
Jest isn’t picking up that the mock is being called
I find it is tricky as well to get test running with ant design forms. After some digging and experiments, it seems simulating 'click' doesn't trigger the onFinish, but instead, simulating 'submit' does. Imaging the following example using Jest and Enzyme:
export default MyForm = () => {
<Form onFinish={onFinish}>
<Form.Item>
<Button htmlType='submit'>Submit</Button>
</Form.Item>
</Form>
}
it('should trigger onFinish if submit', () => {
const onFinish = jest.fn();
const wrapper = mount(<MyForm onFinish={onFinish}/>);
wrapper.find('button').simulate('submit');
await sleep(200)
expect(onFinish).toHaveBeenCalled();
})
Myself is also struggling with the first issue:
Warning: An update to Login inside a test was not wrapped in act(...)
Note that this will only happen if there is validation error because there is internal state change in their implementation of the Form.
First of all, this is a warning meaning that you can ignore it if you don't mind the log to be polluted.
Secondly, this is a warning only when it is 'development' but not 'production'.
If you indeed want to make it disappear, this is the "hacky" way (I don't know if there is better way) I am taking:
it('should trigger onFinish if submit', () => {
const onFinish = jest.fn();
const wrapper = mount(<MyForm onFinish={onFinish}/>);
await act(async () => {
wrapper.find('button').simulate('submit');
})
await sleep(200)
wrapper.update()
// check errors here
expect(onFinish).not.toHaveBeenCalled();
})
This is officially documented here: https://github.com/enzymejs/enzyme
I had some trouble too submitting forms with Ant Design 4 + react-testing-library. After some experimentation I ended with always using data-testid to submit forms :
<Button data-testid="submit" htmlType="submit">Submit</>
// Form element has onFinish attribute
// in test
fireEvent.click(rtl.getByTestId("submit"));
expect(...);
This is not what RTL promotes (datat-testid should be avoided, test as a real user would) but I can live with that.
But according to https://github.com/ant-design/ant-design/issues/21272 firing a submit event should work too, I will definitely give a try.
Bordering on comedic, and definitely NOT the proper way to solve the problem. But A way to solve the problem - the only one that worked for me besides avoiding using onFinish entirely and relying on onClick on the button.
So what did the trick is actually wrapping the expect(...) in a setTimeout(...)
// click || submit - both seemed to work for me
fireEvent.submit(submitButton);
setTimeout(() => {
expect(fetchSignIn).toHaveBeenCalledWith('asf');
});
it('should call props.fetchSignIn with the username and password', async () => {
const promise = Promise.resolve({
email: 'test#circulate.social',
firstName: 'Mike',
lastName: 'A',
});
const fetchSignIn = jest.fn(() => promise);
const { queryByTestId, queryByPlaceholderText } = renderLogin({
seedEmail: 'aasdf',
seedPassword: 'asdf',
fetchSignIn,
});
const emailInput = queryByPlaceholderText('joedoe#gmail.com');
const passwordInput = queryByPlaceholderText('password');
const submitButton = queryByTestId('submitButton');
fireEvent.change(emailInput, {
target: { value: 'mike#circulate.social' },
});
fireEvent.change(passwordInput, { target: { value: 'Password1!' } });
// click || submit - both seemed to work for me
fireEvent.submit(submitButton);
setTimeout(() => {
expect(fetchSignIn).toHaveBeenCalledWith('asf');
});
await act(() => promise);
});
This was brought up to me by someone on the GitHub issue
https://github.com/ant-design/ant-design/issues/21272#issuecomment-628607141 so shoutout to #ildarnm on GH
I would like to have a AuthWrapper Service that wraps the AngularFireAuth Service. Something like this.
// EDIT: Adding some import statements.
import {TestBed} from '#angular/core/testing';
import { AuthWrapperService } from './auth-wrapper.service';
import {AngularFireModule} from '#angular/fire';
import {AngularFireAuth, AngularFireAuthModule} from '#angular/fire/auth';
import {environment} from '../environments/environment';
#Injectable({
providedIn: 'root'
})
export class AuthWrapper {
constructor(public afAuth: AngularFireAuth) { }
isAuthenticated(): Observable<firebase.User> {
return this.afAuth.user;
}
createUserWithEmailAndPassword(email: string, password: string): Promise<String> {
let authPromise: Promise<firebase.auth.UserCredential> =
this.afAuth.auth.createUserWithEmailAndPassword(email, password);
return authPromise
.then((value) => {
return value.user.email;
})
.catch(function(error) {
return error.message;
});
}
}
I want a wrapper, so that I can test my connection to AngularFireAuth. Other tests mock the AuthWrapper.
( Reason for not mocking AngularFireAuth: Say I mock AngularFireAuth, then I am determining the mock's return values. Then I am saying that I understand what these values would look like. It is not safe to assume this without ever testing these by calling the backend. Say google changes how the results of the real AngularFireAuth's methods, I would then be forced to change the results of each of my AngularFireAuth mocks. Instead it is better to wrap AngularFireAuth in a wrapper, and just change that wrapper's methods to conform to google's changes. )
My tests in Karmine and Jasmine result in an "Async callback was not invoked within 5000ms error." I know the user is signed it because the first expect passes, but how do I get the second expect to work?:
describe('AuthWrapperService', () => {
let fireAuthService: AngularFireAuth;
let password = "dcbA4321!";
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
AngularFireModule.initializeApp(environment.firebase),
AngularFireModule,
AngularFireAuthModule
],
providers: [AngularFireAuth, AuthWrapperService],
declarations: []
})
fireAuthService = TestBed.get(AngularFireAuth);
});
it('should sign in user', async() => {
const email = "anyemail#gmail.com";
let authWrap = new AuthWrapperService(fireAuthService);
let userEmail = await authWrap.createUserWithEmailAndPassword(email, password);
expect(userEmail).toBe("anyemail#gmail.com");
let obs = authWrap.isAuthenticated();
let promise = obs.toPromise();
let user = await promise.then((result) => {return result});
expect(user.email).toBe("anyemail#gmail.com");
});
});
I haven't seen any Angular testing code, where the AngularFireAuth isn't mocked.
These are the tests that worked for me with a time interval of 5000ms. I did test them with "bad#gmail.com" in the expect lines to make sure the expect lines were actually running, and yes, the expect lines are actually passing. I intend to only run the AuthWrapperService tests periodically since they hit the database. I will mock the AuthWrapperService in other tests. Before I run these tests I delete the existing users in the firebase database manually through the firebase console. I do run both of these tests in one go, (inside one describe block).
it('should create new user', async() => {
const annEmail = "ann#gmail.com";
let authWrap = new AuthWrapperService(fireAuthService);
await authWrap.createUserWithEmailAndPassword(annEmail, password).then(
((testEmail) => {
expect(fireAuthService.auth.currentUser.email).toBe(annEmail);
})
)
await fireAuthService.auth.signOut();
});
it('should return error message when user already exists', async() => {
const email = 'ben#gmail.com';
const error = 'already in use';
let authWrap = new AuthWrapperService(fireAuthService);
await authWrap.createUserWithEmailAndPassword(email, password)
.then((value: String) => {
expect(fireAuthService.auth.currentUser.email).toBe(email);
});
await fireAuthService.auth.signOut();
expect(fireAuthService.auth.currentUser).toBeNull();
await authWrap.createUserWithEmailAndPassword(email, password)
.then((value: String) => {
expect(value).toContain('already in use');
});
await fireAuthService.auth.signOut();
});
I decided to use fireAuthService.auth.currentUser.email instead of the AuthWrapperService's isAuthenticated() method to directly test that my AuthWrapperService's createUserWithEmailAndPassword() method creates a user in firebase.
I realize that this is not a unit test, but I have decided that it is better to have an integration test than to mock methods that I don't own.
The following test also worked for me within 5000ms and uses the isAuthenticated() method.
it('should create user cat', async() => {
const catEmail = "cat#gmail.com";
let authWrap = new AuthWrapperService(fireAuthService);
let userEmail = await authWrap.createUserWithEmailAndPassword(catEmail, password);
expect(userEmail).toBe(catEmail);
await authWrap.isAuthenticated().subscribe({
next(user) {
expect(user.email).toBe(catEmail);
}
});
console.log("cat, timeinterval: " + jasmine.DEFAULT_TIMEOUT_INTERVAL);
await fireAuthService.auth.signOut();
});
The following "dan test" resulted in time outs. I tried 29000ms once and it ran out of time. Most runs of this test were 10 seconds. Many times, but not always, this test passes correctly if the user is already a user before the test is run (so it's not really creating a user during the test). One time this test passed correctly when the user had not been created before the test, but I ran this many times without the user existing before the test and it failed. I'm not sure why that would be.
it('should create user dan', done => {
const datEmail = "dan#gmail.com";
let authWrap = new AuthWrapperService(fireAuthService);
authWrap.isAuthenticated().subscribe(
{
next(user){
expect(user.email).toBe(datEmail);
done();
}
}
);
authWrap.createUserWithEmailAndPassword(datEmail, password);
console.log("dan, timeinterval: " + jasmine.DEFAULT_TIMEOUT_INTERVAL);
});
As discussed in comments, its not a good practice to not mock dependencies in case of unit testing. Any cohesive unit should be tested in isolation. And let say if our dependency has number of other dependencies then we cannot provide them all.
When you test the code with the actual dependency, you are not doing unit testing; you are doing integration testing.
Please see this for difference between Integration testing and Unit testing - https://angular.io/guide/testing#use-e2e-end-to-end-to-test-more-than-a-single-unit
You are facing this error because your async spec finishes after the default time out that jasmine has specified, which is 5 seconds. Please find the jasmine documentation on this. URL - https://jasmine.github.io/2.0/introduction.html#section-42
You need to manipulate the jasmine.DEFAULT_TIMEOUT_INTERVAL as per your need, assign it some large value as shown in below example:
describe("long asynchronous specs", function() {
var originalTimeout;
beforeEach(function() {
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; // 60 second (use value as per need)
});
it("takes a long time", function(done) {
setTimeout(function() {
done();
}, 9000);
});
afterEach(function() {
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
});
});
It still depend on the network as well how fast your promise returns the value. I would suggest you to change the value for jasmine.DEFAULT_TIMEOUT_INTERVAL only in test where you are working with actual dependecies.
I hope this will help.
So i have this little function
module.exports = {
setupNewUser(info, callback) {
var user = {
name: info.name,
nameLowercase: info.name.toLowerCase()
}
try {
Database.save(user, callback)
}
catch(err) {
callback(err)
}
}
}
and im using sinon to test this method
const setupNewUser = require('./index').setupNewUser
const sinon = require('sinon')
const assert = require('assert')
const Database = {
save(info, cb) {
if (info === undefined) {
return cb('nope')
} else {
return cb()
}
}
}
describe('#save()', function () {
it('should call save once', function() {
var save = sinon.spy(Database, 'save')
setupNewUser({ name: 'test' }, function() { })
save.restore()
sinon.assert.calledOnce(save)
})
})
When i ran the test it fail any knows why ?
Error message
AssertError: expected save to be called once but was called 0 times
I believe the reason this is happening is because you're not actually stubbing out the method you think you are. In your test code, your intention was to create a fake Database object so that your actual source code will call this object's method. What you need to stub out is that actual Database object that your source code uses.
Normally in your source code, you'd probably import the Database object. You'll need to import the same Database object and stub it out in your test code as well.