I'm trying to write Jest tests for a React component which contains a DashJS media player. I'm using Enzyme's mount method to try and test the component, but it seems that the DashJS media player fails to mount properly.
In my componentDidMount method, I have the following code:
this.videoManager = dashjs.MediaPlayer().create();
this.videoManager.initialize(this.videoPlayer, videoUrl, true);
// Where this.videoPlayer is a reference to an HTML <video> element
this.videoManager.preload();
The last line (this.videoManager.preload();) produces the following error:
You must first call attachSource() with a valid source before calling this method thrown
When I run the component it works normally - it's only the testing I'm having issues with. I haven't been able to find any related issues/solutions online.
I'm using the following versions of each relevant package:
react: "16.2.0"
dashjs: "2.6.7"
jest: "22.3.0"
enzyme: "3.3.0"
enzyme-adapter-react-16: "1.1.1"
Any help will be appreciated!
That error implies that there was some issue with videoUrl, which caused the value passed in initialize not to be set. When preload checks that a valid source has been set, the error is thrown.
At a guess, is videoUrl an empty string in your test but non-zero length when the component is used normally?
Looking at this again, the problem is probably that you are (presumably) using JSDOM to provide the DOM for your tests, and JSDOM does not provide MediaSource or WebKitMediaSource in window. This causes dash.js to fail to initialise. dash.js should throw a capability error which can be caught using player.on('error', () => {}).
As a side note, you are providing a video object to initialize, as well as setting autoplay to true. Doing the first will cause preload do nothing since it will just load segments in to the SourceBuffer instead, which probably isn't what you wanted.
Related
I created this CodeSandbox so I could demonstrate a problem with related code (the onChange not firing in a test):
https://codesandbox.io/s/festive-tree-xkw8s?file=/src/App.test.js
However in CodeSandbox I can't even get that far because as soon as I call
userEvent.type(securityField, 'abc{enter}')
I get a set this error in the console:
Error: Uncaught [TypeError: Failed to execute 'getComputedStyle' on 'Window': parameter 1 is not of type 'Element'.]
and as a result the loadOptions callback of AsynSelect is never triggered (locally I'm not having any issue with that, just the onChange which isn't firing).
I have not been able to find any examples of or guidance on this error. I haven't used CodeSandbox a lot so maybe I'm missing something basic.
I am not seeing this error in my local environment but I am trying to solve it because I want to finish setting up my original problem.
You want to use react-select-event for interacting with your react-select in testing. This was made specifically for testing with react-testing-library.
Our React-Redux App uses create-react-app. We’re trying to back-apply Jest/Enzyme/redux-mock-store to a large code base. Our src/store.js file is the standard react-redux script that runs the reducers and constructs an initial state. This src/store.js is central to the problem.
Problem is that when we start the jest test-run, jest loads src/store.js 7 times, and then (roughly) once for every test. I must suppress this and can’t figure out how. The reason I must suppress it is that it always leaves the store in its initial state and there is one crucial AppConfig property in that store which must be populated to prevent javascript errors that stop jest. I already have a function which returns a mock store for use with the Provider. That works well. But our components call out from constructor(), componentDidMount(), etc to api functions. Those api functions don’t see the state dropped from Provider. Instead, the api functions do stuff like this:
// AppConfig is an immutable Map
const { AppConfig } = store.getState();
const stuff = AppConfig.get(‘stuff’).toJS();
That’s a JS error because AppConfig is empty and toJS() is called on undefined. (That can never be a problem in run-time because we construct no component without AppConfg being ready.) There are 2 solutions involving refactoring the App that I can do. But I’d rather figure out how to properly use jest. How can I supply the mock redux state without it getting clobbered by jest's repeated loads of src/store.js???
This is killing me. What in Jest, create-react-app, or ??? initiates these unwanted loads of src/store.js? How to override? Is there a config that causes jest to call my function instead of src/store.js?
I figured out my own problem. Will post here in case someone else that is new with jest may benefit from my folly.
Jest does not load src/store.js. The culprit is my shallow component tests because the components have statements like this:
import StuffService from '../../api/stuff';
The calls to the api from the component look like this:
// e.g. from componentDidMount
StuffService.get.myRequest();
But in the es6 module that defines StuffService, at the top we have something like:
import store from '../../store';
// lower in the js file, we have code as shown in my previous post:
const { AppConfig } = store.getState();
That's what is causing src/store to keep running. Will study Jest's Manual Mocks for a solution.
Solved
Issue is tracked on github
I was attempting to test custom hooks using the react testing library, and the hook I was trying to put under test requires multiple context providers to work correctly. (authentication and notification)
The documentation here only outlines creating a wrapper with a single provider like so:
const wrapper = ({ children }) => <ContextProvider>{children}</ContextProvider>
However, my implementation needed something more complex, like so:
const wrapper = ({ children }) => (
<ToastProvider>
<NotificationProvider>
<AuthProvider>{children}</AuthProvider>
</NotificationProvider>
</ToastProvider>
);
This was was failing at every attempt with the errors:
TypeError: parentInstance.children.indexOf is not a function
OR
Invariant Violation: Drop(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
Leading me to believe there was no clear way to provide the right context without abandoning renderHook all together and building a test component that could fire off the necessary behaviors manually.
After a lot more digging I found this error buried in the logs:
Warning: An invalid container has been provided. This may indicate that another renderer is being used in addition to the test renderer. (For example, ReactDOM.createPortal inside of a ReactTestRenderer tree.) This is not supported.
Surely enough, it turns out there is a conflict with react-test-renderer and react-dom which causes calls to ReactDOM.createPortal to fail when under test. I assume this is somewhere in the ToastProvider, but the workaround is pretty simple.
Solved by adding this to the top of my test:
ReactDOM.createPortal = node => node
The pattern to check if a React component contains something with Enzyme + Jest seems to be this one:
expect(shallow(<Field title="hello" />).contains(<p id="hello"></p>)).toBe(true);
The main problem I face with this kind of code is that I have no clue what the problem is if the expectation fails. Because it uses the pattern "expect boolean to be boolean", the only error I get is:
expect(received).toBe(expected) // Object.is equality
Expected: true
Received: false
It does not really help. Is there a way to get more precise error when testing if a React element contains something?
Yes, there are more ways of checking if the element is present within the component. And if your test case fails you can debug the wrapper to check what went wrong. .debug() won't pinpoint the error but I think it will make debugging very easy since you get what is being rendered by shallow/mount.
The following command will help you debug a wrapper.
const wrapper = shallow(<Field title="hello" />);
console.log(wrapper.debug());
I have created a sandbox to demonstrate the same there I use 'contains' and 'find' on the wrapper to check if p is present with id as "hello" and also on the console you can see the output of wrapper.debug()
https://codesandbox.io/s/886799791j
TypeError: Cannot create property '__mobxInstanceCount' on boolean 'true'
at Object.<anonymous> (node_modules/mobx/lib/mobx.js:2620:38)
at Object.<anonymous> (src/Stores/DashboardStore.ts:9:14)
at Object.<anonymous> (src/__tests__/DashboardStore.test.ts:3:24)
Test Suites: 1 failed, 1 total
mobx.js:2620 mentions about global multiple instance.
Dashboard.test.ts
import DashboardStore from '../Stores/DashboardStore';
describe('DashboardStore', () => {
let dashboardStore;
beforeEach(() => {
dashboardStore = DashboardStore;
});
// tslint:disable-next-line:no-console
console.log(dashboardStore);
});
FAIL src\__tests__\DashboardStore.test.ts
? Test suite failed to run
My request is provide some hint on how to unit test a component and store.
Thanks you.
I don't know why, for me it was a problem in jest configuration file, when I tried to declare window as global.
It looks like your code imports mobx multiple times (looking at where this error comes from).
The messages seems to originate here out of the source: https://github.com/mobxjs/mobx/blob/master/src/core/globalstate.ts
The fail only triggers if __mobxInstanceCount is already set which means 2 different files globalstate.ts are included (maybe 2 different versions of mobx?)
aditional tips
Im not sure about jest, but with mocha the tests can run at the same time.
This means that the beforeEach does in this case not always ensure a clean dashboardStore variable. (beoreEach could run twice and then the 2 tests can run afterwards, giving issues, because there is only dashboardStore variable and is overwritten on every beforeEach() call).
Inject method
If you really want to make unit tests for your react components. i'd advise to abstract away from directly using the mobx store by using the #inject function instead: https://github.com/mobxjs/mobx-react#inject-as-function
This way you don't need to mock any mobx Store, but only need to insert the needed props in the react components. (keeping your components more declarative in what they really need.)