On which level should I start/stop writing tests? - reactjs

So I started to use react-testing-library and I really like the idea to test user actions, not implementation details.
What I'm really struggling with is where to start writing test ? To be more precise: on which level in the component tree I should start writing my tests?
Let's take the following code es an example:
OrderCreatePage
function CreateOrderPage() {
const [stepOneFrom, setStepOneForm] = useState({});
const [stepTwoFrom, setStepTwoForm] = useState({});
const [stepThreeFrom, setStepThreeForm] = useState({});
const [step, setStep] = useState(1);
const previousStep = () => {
setStep(prev => prev - 1);
}
const nextStep = () => {
setStep(prev => prev + 1);
}
const createOrder = () => {
endpoint.createOrder({
stepOneForm,
stepTwoForm,
stepThreeForm
});
}
return (
<div>
{step === 1 &&
<StepOne
form={stepOneForm}
onNextStep={nextStep}
onFormChange={setStepOneForm}
/>
}
{step === 2 &&
<StepTwo
form={stepTwoForm}
onNextStep={nextStep}
onPreviousStep={previousStep}
onFormChange={setStepTwoForm}
/>
}
{step === 2 &&
<StepTwo
form={stepTwoForm}
onPreviousStep={previousStep}
onCreateOrder={createOrder}
onFormChange={setStepTwoForm}
/>
}
</div>
);
}
StepOne
function StepOne(props) {
const isValidForm() => {
return // do some checks on props.form
}
const handleNextClick = () => {
if(isValidForm()){
props.onNextStep();
}
}
return (
<div>
<ArticlesForm form={props.form} onFormChange={props.onFormChange}/> // StepTwo and StepThree e.g. have DeliveryForm and PaymentForm
<button onClick={props.nextStep}>Next</button>
</div>
);
}
For StepTwo and StepThree, just imagine them to be similar to StepOne.
ArticleForm in the above example is declaring all the input fields etc. and is updating the form values.
Think of all components to be much more complex and don't take this example to serious. In general there are 3 level to write the tests (from lowest to highest level)
Form-level aka ArticleForm: test if form is updated properly
Step-level aka StepOne: test step independently from other steps, ensure that you are only allowed to go to next step when form is valid
Page-level aka OrderCreatePage: test transitions of steps (including validation checks) and check if you can create the order
So where to start testing here?
If you write test for ArticleForm then you don't test validation, thus you need to write tests for StepOne. If you already wrote test for ArticleForm you would need to basically copy the logic of filling the input fields from that test which make ArticleFrom tests kind of useless. Okay let's skip ArticleForm tests then.
For the transitions you need to test OrderCreatePage now. This time you need to copy the logic of entering valid/invalid data from StepOne (and StepTwo, StepThree) tests, otherwise you are not able to proceed/check transitions.
So just remove the StepOne (and StepTwo, StepThree) tests.
But this result in a big test file for OrderCreatePage and that's not anywhere close to a unit test any more.
I would really appreciate any help here, because I always end up with this kind of thoughts..

Well there is no formula for writing good and efficient tests, some measure coverage and then you should start testing the form-level to reach a good coverage percentage.
I like the approach of the author of the library you are using (react-testing-library) https://kentcdodds.com/blog/write-tests
Write tests. Not too many. Mostly integration.
With your example I would try to look at it not from the code perspective, but from a user perspective :
happy flow : User should be able to complete all steps and submits the form (some mocking might be necessary there)
edge case: User should not be able to create order if not all fields are completed / step not validated etc.
In general there are many right answers to this question so keep an open mind, experiment and learn, there is no silver bullet here

Related

Should I create a context for something that is mostly useCallback or simply create a hook?

I'm just doing a bit of refactoring and I was wondering if I have a bunch of useCallback calls that I want to group together, is it better do it as a simple hook that I would reuse in a few places?
The result would be
interface IUtils {
something(req: Something) : Result;
somethingElse(req: SomethingElse) : Result;
// etc...
}
So a plain hooks example would be:
export function useUtils() : IUtils {
// there's more but basically for this example I am just using one.
// to narrow the focus down, the `use` methods on this
// block are mostly getting data from existing contexts
// and they themselves do not have any `useEffect`
const authenticatedClient = useAuthenticatedClient();
// this is a method that takes some of the common context stuff like client
// or userProfile etc from above and provides a simpler API for
// the hook users so they don't have to manually create those calls anymore
const something = useCallback((req:SomethingRequest)=> doSomething(authenticatedClient), [authenticatedClient]
// there are a few of the above too.
return {
something
}
}
The other option was to create a context similar to the above
const UtilsContext = createContext<IUtils>({ something: noop });
export UtilsProvider({children}:PropsWithChildren<{}>) : JSX.Element {
const authenticatedClient = useAuthenticatedClient();
const something = useCallback((req:SomethingRequest)=> doSomething(authenticatedClient), [authenticatedClient]
const contextValue = useMemo({something}, [something]);
return <UtilsContext.Provider value={contextValue}>{children}</UtilsContext.Provider>
}
The performance difference between the two approaches are not really visible (since I can only test it in the device) even on the debugger and I am not sure how to even set it up on set up on jsben.ch.
Having it as just a simple hook is easier I find because I don't have to deal with adding yet another component to the tree, but even if I use it in a number of places I don't see any visible improvement but the devices could be so fast that it's moot. But what's the best practice in this situation?

How to use react horizontal timeline for other things than data?

In react horizontal timeline is written that there should be use only date. But I have project where I want to display how we work step by step.
I guest that always there is possibility to modified some logic of code, if programmer that want to.
I have two idea:
first use vanilla javascript to render on place date, text i.e ["2018-02-23" => "contact us"]
const [value, setValue] = useState(["2000-02-23", "2001-03-22", "2002-03-23", "2003-03-23", "2004-03-26", "2005-03-27", "2005-03-28", "2006-03-29",]);
if (value[0] = "2000-02-23") {setValue( prev => prev[0] = "kontakt", ...prev)}
but it throw me an error that the object do not exist, so I can do noting with that.

StencilJS unit testing keyboard event?

I want to write unit tests for my custom web-components in stencilJs but have no idea how to do it the right way. Here's what I did so far!
.tsx
...
valueFormat(event: Event): void {
const val = (event.target as HTMLInputElement).value;
const format = Number.parseInt(val, 10);
const newVal = format.toLocaleString(undefined, {
minimumFractionDigits: 2,
});
this.value = newVal;
}
.spec.tsx
it('should format value', async () => {
const comp = new MyComponent();
const spy = jest.spyOn(comp, 'valueFormat');
comp.myInputEvent.emit();
expect(spy).toHaveBeenCalled();
});
I want to test the case, when I type a number in the input field that it format it. So my valueFormat() method, I spying on should be called when a Keyboard event is firing. I hope you can help me out!
If you want to test it with Event in mind, I would strongly recommend you to use newSpecPage(https://stenciljs.com/docs/unit-testing) - as this will allow you to construct your component DOM in memory and allow you to test its logic (so you can easily trigger event like click, keyboard or trigger input value change which I assume where your valueFormat() method get called/binded?)
Another approach is to move formatting logic to separate function which takes just input value as an argument like:
formatInputValue(value: string) {
const format = Number.parseInt(value, 10);
const newVal = format.toLocaleString(undefined, {
minimumFractionDigits: 2,
});
return newVal;
}
then you could easily unit test this method by simply constructing component and then calling the method with whatever the value you want to test with (this is useful if you want to test edge cases like null, empty value, non numeric value etc.)
Personally I wouldn't bother creating function as conversion logic seem to be simple - also one advantage of doing testing via DOM (using newSpecPage()) is that if you ever want to change your formatting logic, amount of test code you need to update could be quite small, meaning your test code is bit more maintainable (again just my personal opinion, it's all depends on how complex the formatting logic or the expected input be)

Find element by id in react-testing-library

I'm using react-testing-libarary to test my react application. For some reason, I need to be able to find the element by id and not data-testid. There is no way to achieve this in the documentation.
Is there a way to achieve this?
I have the rendered output as follows:
const dom = render(<App />);
I'm looking for something along the lines of:
const input = dom.getElementById('firstinput');
//or
const input = dom.getById('firstinput');
I feel like none of the answers really gave a complete solution, so here it is:
const result = render(<SomeComponent />);
const someElement = result.container.querySelector('#some-id');
I found a way to do this.
import App from './App';
import { render, queryByAttribute } from 'react-testing-library';
const getById = queryByAttribute.bind(null, 'id');
const dom = render(<App />);
const table = getById(dom.container, 'directory-table');
I hope this helps.
It looks you have DOM node itself as a container. Therefore, you should be able to call .querySelector('#firstinput') with that.
There are two ways to do so
Simply use container.getElementById('id'). In the end, all the helpers are doing is making queries like this one under the hood
If you want to have your custom query you can write a custom render. Check the documentation for more info https://github.com/kentcdodds/react-testing-library#getbytestidtext-textmatch-htmlelement
As a final note, if you can avoid looking for elements by id it's better.
You can set up with testIdAttribute in the configuration.
configure({ testIdAttribute: 'id' })
https://testing-library.com/docs/dom-testing-library/api-configuration
The setting has pros and cons. The benefit of it is that you can set an id for multiple uses. (Test id, marketing analytics, tag manager, ...etc) You don't have to add both id and test-id. It's good for the conciseness of the code.
But be careful, you might accidentally set the same id at two different components on the same page. Remember to add index or identification to a component id for list items.
My advice: stop adding and searching by ids, this always takes to much time and effort because you have to add the ids (sometimes test-ids) and then find out the best way to query the element. But even if you really need an id, this tool will save you a lot of time by showing the best way to query any DOM element on your screen: Testing Playground
If you use TypeScript, and want to get a non-null result, here's a convenience function:
function getById<T extends Element>(container: HTMLElement, id: string): T {
const element = container.querySelector<T>(`#${id}`);
assert(element !== null, `Unable to find an element with ID #${id}.`)
return element;
}
You can then use it like this:
import { render } from '#testing-library/react';
const { container } = render(<App />);
const myInputElement = getById<HTMLInputElement>(container, 'myInputElement');

How to update variables using createRefetchContainer?

Since the React Relay createPaginationContainer does not support offset-based pagination, the next best option would be to handle this feature through the use of the createRefetchContainer.
In the example provided on the Relay Modern documentation https://facebook.github.io/relay/docs/refetch-container.html, when implemented will paginate forward one time, but only because we are transitioning from our default state at offset of 0 to our new state of 0 + 10. Subsequent click events produce the same result since the values are not being stored.
I would have expected that the offset value would continue to increment but it does not appear that the state is being maintained through each refetch.
I came across this issue on the repo which seems to have addressed this, https://github.com/facebook/relay/issues/1695. If this is the case then the documentation has not been updated.
While I believe there should be a built in mechanism for this, I ultimately ended up storing values in state and using callbacks to trigger my refetch.
So in the example above which I listed from the documentation the update appears to happen here:
_loadMore() {
// Increments the number of stories being rendered by 10.
const refetchVariables = fragmentVariables => ({
count: fragmentVariables.count + 10,
});
this.props.relay.refetch(refetchVariables, null);
}
So the issue I have with this particular example is that we are pulling the default state from the fragmentVariable so in essence no real change is ever occurring. This may be acceptable depending on your implementation but I feel that for most use cases we would like to see values being actually updated as variables in the updated fragment.
So the way I approached this in terms of my offset-based pagination was...
_nextPage = () => {
if ((this.state.offset + this.state.limit) < (this.state.total - this.state.limit) {
this.setState({ offset: (this.state.offset + this.state.limit), () => {
this._loadMore();
}
}
}
_loadMore = () => {
const refetchVariables = {
offset: this.state.offset,
limit: this.state.limit
}
this.props.relay.refetch(refetchVariables, null);
}
May have a typo, I'm not actually looking at my code right now. But by using the state of the component, you will effectively be able to update the variables of the refetchContainer.

Resources