We have UI client tests that cover everything from API, the business logic layer, as well as testing any local logic in higher order and UI components.
Some examples:
- At the API layer, it mocks the network layer and tests that, when triggered, it sends the correct request and given a response, handles it correctly (both for success and fail responses, as well as different expected data).
- business logic layer is some of the most important tests, this layer will test things like how data flows through the app and how it might be transformed along the way for use in the UI. This layer generally sits between the API and UI, so it mocks functions in the API stack and tests various responses, tests streams of data. This layer often is your "traditional unit tests" - testing individual functions to ensure they are idempotent, no (unintended) side effects creep into the code, putting in X gets back Y, etc.
- Higher order UI components - these are components that, while they may have some UI (yah, I know, not purely higher order), they are generally the component that acts as an intermediary between the business logic and more pure UI (display) components. The tests here would mock any calls into the business logic, make sure it properly requests the needed data and then check to see that any local logic is correct. This layer may be handling route params or query params, and adjusting the data request based on that or other data stored in memory or in the app. It may be handling collection of form data and giving it to a business logic function, and handling any errors that may send back, and the tests will check that. It often has functions that are triggered by actions in display components, and rather than try to wire components together in tests (we do have some e2e tests that check at that level), it's better to test those functions in isolation at this layer.
- Display UI components - I don't want to assume, but based on the comments, this is likely what you have thought of when you think of UI testing. At this layer, there should be much less logic in each component. It usually has data inputs, and then renders that into a template. We don't "test the framework" - there's no sense testing that an input prop is "set" correctly. But if the UI component does anything with that prop to prepare it for display, we'll test that. Or if it has UI actions - this is one place we will sometimes tie to the DOM and test things like click events, but most of the time that isn't really necessary (again, we make as assumption that if we properly wire up a click handler, it will fire on click). So many times we will just test the handler themselves, if they do any logic prior to emitting that value back out to a higher order component or to the business logic layer. We try to keep these simple, but there are sometimes tests here that are testing that expected DOM elements are visible based on the state of input data, or that the expect text is visible based on user action (i.e. did a panel display text when the user clicked it open). This is the layer we have to be fairly careful not to write "lazy" tests (did the framework render the text assigned to a variable and interpolated in the template isn't super valuable).
If it matters (and I don't think it should, because the practice of writing good tests, even for client apps, isn't specific to language/framework) our primary apps right now are written in Flutter and Angular. We also use or have used Swift and Kotlin (iOS/Android respectively), Java, React (little bit of React Native but quickly abandoned in favor of Flutter), and Ember (had a pretty big test suite for an Ember app, since shut down though). And, despite all of the above, most of our logic does not actually reside on the client side but rather lives in the API/backend and we are mindful to try to keep as much logic there as possible. And we have good test coverage there as well. Of course, for all our code, the coverage could always be better.
Anyways, I can't really pull code samples as the apps are proprietary (and this is getting long enough as it is), but I hope that answers your question and helps illustrate at least why I believe there can be significant value in client side tests.
Some examples:
- At the API layer, it mocks the network layer and tests that, when triggered, it sends the correct request and given a response, handles it correctly (both for success and fail responses, as well as different expected data).
- business logic layer is some of the most important tests, this layer will test things like how data flows through the app and how it might be transformed along the way for use in the UI. This layer generally sits between the API and UI, so it mocks functions in the API stack and tests various responses, tests streams of data. This layer often is your "traditional unit tests" - testing individual functions to ensure they are idempotent, no (unintended) side effects creep into the code, putting in X gets back Y, etc.
- Higher order UI components - these are components that, while they may have some UI (yah, I know, not purely higher order), they are generally the component that acts as an intermediary between the business logic and more pure UI (display) components. The tests here would mock any calls into the business logic, make sure it properly requests the needed data and then check to see that any local logic is correct. This layer may be handling route params or query params, and adjusting the data request based on that or other data stored in memory or in the app. It may be handling collection of form data and giving it to a business logic function, and handling any errors that may send back, and the tests will check that. It often has functions that are triggered by actions in display components, and rather than try to wire components together in tests (we do have some e2e tests that check at that level), it's better to test those functions in isolation at this layer.
- Display UI components - I don't want to assume, but based on the comments, this is likely what you have thought of when you think of UI testing. At this layer, there should be much less logic in each component. It usually has data inputs, and then renders that into a template. We don't "test the framework" - there's no sense testing that an input prop is "set" correctly. But if the UI component does anything with that prop to prepare it for display, we'll test that. Or if it has UI actions - this is one place we will sometimes tie to the DOM and test things like click events, but most of the time that isn't really necessary (again, we make as assumption that if we properly wire up a click handler, it will fire on click). So many times we will just test the handler themselves, if they do any logic prior to emitting that value back out to a higher order component or to the business logic layer. We try to keep these simple, but there are sometimes tests here that are testing that expected DOM elements are visible based on the state of input data, or that the expect text is visible based on user action (i.e. did a panel display text when the user clicked it open). This is the layer we have to be fairly careful not to write "lazy" tests (did the framework render the text assigned to a variable and interpolated in the template isn't super valuable).
If it matters (and I don't think it should, because the practice of writing good tests, even for client apps, isn't specific to language/framework) our primary apps right now are written in Flutter and Angular. We also use or have used Swift and Kotlin (iOS/Android respectively), Java, React (little bit of React Native but quickly abandoned in favor of Flutter), and Ember (had a pretty big test suite for an Ember app, since shut down though). And, despite all of the above, most of our logic does not actually reside on the client side but rather lives in the API/backend and we are mindful to try to keep as much logic there as possible. And we have good test coverage there as well. Of course, for all our code, the coverage could always be better.
Anyways, I can't really pull code samples as the apps are proprietary (and this is getting long enough as it is), but I hope that answers your question and helps illustrate at least why I believe there can be significant value in client side tests.