3

How to Perform Unit Tests for UI Components

 1 year ago
source link: https://blog.bitsrc.io/use-integration-test-instead-of-unit-test-for-ui-components-1eee69c0b2ed
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

How to Perform Unit Tests for UI Components

0*ZyNDgAJRh3i2j2lj

Photo by Alexas_Fotos on Unsplash

I think I have written articles about the problem of writing unit tests for UI components. Here’re a few articles that I wrote that lead to the conclusion of this article here:

So the main issue can be demonstrated via the following component snippet:

function App() {
const [user] = useAuth() (3)
const onClick = () => {} (1)
axios.get().then(() => {}) (2)

return DomLike
}

In a unit test, we want to check the return elements from the render of the component; however, there are too many hidden dependencies we don’t see from the explicit interface of App function, including:

  • (1) global dom-related dependencies
  • (2) API-related dependencies
  • (3) framework wise dependencies

Believe it or not, most of us can figure out one or two of the above hidden dependencies, but not all of them. We are also, most of time, vague about the “hidden” label. Sometimes people might stay in the gray area not calling them dependency. But they are TRUE dependencies.

From the unit test point of view, unless we can mock all of the dependencies, you can’t draft a valid unit test. But even say you draft it successfully, running our common sense here, if we mock all of the above dependencies, is there anything left for us to test? Often, when everything is mocked, you are left with a test similar to 1 === 1 with both the left and right parts manually set by you (aka. circular assertion). This is the reason why I call unit tester a lier when it comes to the UI components. Forgive my language, but I have to be honest about what I do in coding.

What’s the point?

When writing tests, you have to realize in the end you are not trying to get the PASS. What’s valuable is to see whether it’s helpful to do the following two things:

  • Lay down the documentation spec of using the component
  • Simulate the behavior of the component in an isolated environment

In a plain statement, if your test isn’t going to serve the above two rules, it’s better you don’t write the tests in the first place. Because without serving the purpose, you can only add burdens to the development team than values to them. Keep in mind, writing tests has a cost, and that cost needs to be justified through time. Money is a key element in this game. If you don’t understand this concept, it’s better to wait a while without rushing into writing tests. To be honest, not writing tests isn’t a bigger deal than not understanding the tests.

If the unit test isn’t the right tool, what else?

Say we want to handle all the events, if you click an element, we want the event to be executed without mocking. Therefore the code in the event handler can run the same version we wrote:

const onClick = () => {
// my real version
}

In the unit test, there’s no way to practice the event as a real user, so even sometimes we can call the onClick manually, it’s quite ad-hoc. But without triggering the event, how can we run our real version of the handler?

I don’t understand the meaning of not running the real version of my code inside this unit in the test. To me, this is meaningless, that’s the reason you end up with a statement as 1 === 1 . You just cheat out of yourself.

API

APIs are all mocked in the unit test, no question about that. But practically we only want to mock the response part, not the calling part.

axios.get().then(res => {
// my real version
})

Therefore, we need a test tool to help us allow make calls to the internet, but hijack it to return the mocked response, and run our real version of the handler with the response.

Framework

We are not writing components from scratch, everyone is using some sort of UI framework such as React etc. Therefore, context variables, global to the component but local to the framework, can co-exist quite often inside your unit. Since they are out of scope of this component, in a strict matter, they don’t belong to the scope of this unit in terms of the testing.

const [user] = useAuth()

Therefore we can mock them in the unit test but only with the penalty of losing the valuable functionalities. Don’t take these framework-provided utilities lightly, it’s not as same as an external library or service. Functionalities-wise, they are part of the deal, yes they are the meat. Without it, you could sometimes literally have done nothing, therefore excluding these functionalities means there’s nothing to test in the end.

IMHO, strictly following unit test is nonsense in terms of testing the logic of a UI component in practice.

In order for these variables to survive without mocking, we need a test tool to mount the component physically in a browser-like environment under the context of the framework. We can still mock the response (input or output) of this context, but we can’t skip the source code (or implementation) of this context. In a sense, this is like testing against a real database, so testing under a framework pushes the tests straight into the integration/e2e testing category if I really need to label our tests.

Like the term “unit testing,” “integration testing” means different things to different people. As I’ve mentioned before, I recommend you not get too hung up on labels. — quote from book “Testing Javascript Applications” by Lucas Fernandes da Costa

What are the tools

Now we know what we need for the testing. We can look for what works for us. If you don’t mind loosening up the definition of a unit test, Cypress is hard to miss. If you don’t want to go that far, you can use testing-library. To address an individual problem such as API, you can mix in msw, a mock service worker. There’re lots of possibilities, but the bottom line is we need to go beyond the traditional unit test mindset when dealing with UI components.

💡 If you were using a platform such as Bitfor publishing, sharing and reusing your UI components, every component you publish would have unit tests associated with it. Using Bit’s Compositions feature, you can automatically create *.spec.* and recognize *.test.* files, and every component you publish and version on Bit will include tests as essentials, giving you confidence in your code.

Learn more here:

The goal isn’t dodging the unit test requirement to access global services, instead, the goal here is to bypass those services to get back control of running the real version of source code under the testable unit. Now whether you call this a unit test approach or not is really up to you :)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK