From 1e992d7ad36e2cef57a040ef269c40de7e9c17b6 Mon Sep 17 00:00:00 2001 From: "hoeppner.dataport" Date: Wed, 26 Mar 2025 09:07:01 +0100 Subject: [PATCH 1/3] add "mocking pinia-store" to frontend testing --- docs/nuxt-client/5_WritingTests.md | 137 ++++++++++++++++++----------- 1 file changed, 87 insertions(+), 50 deletions(-) diff --git a/docs/nuxt-client/5_WritingTests.md b/docs/nuxt-client/5_WritingTests.md index 82befbfe..f0a7d53e 100644 --- a/docs/nuxt-client/5_WritingTests.md +++ b/docs/nuxt-client/5_WritingTests.md @@ -10,13 +10,13 @@ How to write valuable, reliable tests, that are easy to maintain. Writing good tests that cover all aspects of your code, leads to: -- **confidence**: to refactor your code -- **higher code quality**: as you review your code and identify problems when writing tests -- **well documented code**: as your tests describe how your code works +- **confidence**: to refactor your code +- **higher code quality**: as you review your code and identify problems when writing tests +- **well documented code**: as your tests describe how your code works and by that to: -- **developer happiness** :-) +- **developer happiness** :-) ### Unit-Tests vs. Component-Tests @@ -32,14 +32,14 @@ The enable us to **refactor** the internals of our components later on. ### Positive & negative Tests -- **positive tests** test the default cases of your code = **how it should work** -- **negative tests** test **error-cases** or **exception**-behaviour -- you need to write both to ensure your component works correctly -- think of edge-cases that might break your component e.g. when providing input to the component: - - **numbers**: high numbers, negative numbers, float<->integer, at the edge of a range that is expected... - - **dates**: none existing dates e.g. 30th February 2023, far away future,... - - **strings**: umlauts, url-special-characters (?, &, =, \/\/: ), very long strings for names, long strings without linebreaks - - **totally incorrect data**: e.g. giving a string instead of a number +- **positive tests** test the default cases of your code = **how it should work** +- **negative tests** test **error-cases** or **exception**-behaviour +- you need to write both to ensure your component works correctly +- think of edge-cases that might break your component e.g. when providing input to the component: + - **numbers**: high numbers, negative numbers, float<->integer, at the edge of a range that is expected... + - **dates**: none existing dates e.g. 30th February 2023, far away future,... + - **strings**: umlauts, url-special-characters (?, &, =, \/\/: ), very long strings for names, long strings without linebreaks + - **totally incorrect data**: e.g. giving a string instead of a number ### Use Vue-Test-Utils @@ -47,17 +47,17 @@ For testing our Vue-Components we use the **Vue Test Utils**. Vue Test Utils is Some functionality it provides: -- **mount()**: create a wrapper around the component and instantiate it -- **shallowMount()**: create a shallow wrapper of the component being tested with childcomponents being mocked -- **setMethods()**: mock function on the component -- **setProps()**: set a specific set of props on the component -- **findComponent()**: finds a component by it's class, name or ref -- **findAllComponents()**: finds all components by it's class, name or ref -- **[find() / findAll()](https://v1.test-utils.vuejs.org/api/wrapper/#find)**: search for html elements using html-selectors - - **deprecated for finding Components** - - use findComponent() or findAllComponents() instead -- **setData()**: set specific data on the component -- **trigger()** + **emit()**: test events and the flow of data +- **mount()**: create a wrapper around the component and instantiate it +- **shallowMount()**: create a shallow wrapper of the component being tested with childcomponents being mocked +- **setMethods()**: mock function on the component +- **setProps()**: set a specific set of props on the component +- **findComponent()**: finds a component by it's class, name or ref +- **findAllComponents()**: finds all components by it's class, name or ref +- **[find() / findAll()](https://v1.test-utils.vuejs.org/api/wrapper/#find)**: search for html elements using html-selectors + - **deprecated for finding Components** + - use findComponent() or findAllComponents() instead +- **setData()**: set specific data on the component +- **trigger()** + **emit()**: test events and the flow of data We think the **Vue Test Utils-documentation** is a valuable resource for learning how to test Vue-Components and a very good starting point on how to test certain aspects of your component. Please have a look at [https://test-utils.vuejs.org/guide](https://test-utils.vuejs.org/guide) @@ -81,7 +81,7 @@ Especially in large test-files it is very helpful for the reader to have a tree- 1. describe block that contains the filename in the root-level of the test-file 2. sub-describe-blocks for groups of tests focussing the same aspects of your code -*Example:* +_Example:_ ```TypeScript describe('@components/share/ImportModal', () => { @@ -113,13 +113,13 @@ describe('@components/something/AddButton', () => { }); ``` -**Hint**: *maybe you should extract functionality from your component if this is needed e.g. to find a certain test in your file* +**Hint**: _maybe you should extract functionality from your component if this is needed e.g. to find a certain test in your file_ ### Name the test like a sentence "it should..." There is a reason we use the it-alias for writing our code and not the test-method: we want to describe the aspect that is tested in a natural sentence. That's why it is best practice to start your test with: it('should ...'); -*Example:* +_Example:_ ```TypeScript Bad: @@ -138,10 +138,10 @@ Data-testids are attributes to HTML-elements that are solely used to enable test We decided to unify the way data-testid's should be named in Frontend Arch Group: [Meeting 2022-11-04](https://docs.dbildungscloud.de/x/mYHADQ) -Please use ``
`` in your HTML-code if you want to define a data-testid. +Please use `
` in your HTML-code if you want to define a data-testid. -- do not use uppercase-characters -- only use one dash - right after data +- do not use uppercase-characters +- only use one dash - right after data You can later on check this using: @@ -156,8 +156,8 @@ expect( We also recommend to use refs instead of data-testids. But if you do that you ensure not to remove them once they are in the code... as they can be used in the component-code and for testing: -- [VueJs - template refs](https://vuejs.org/guide/essentials/template-refs.html) -- [VueTestUtils - ref](https://v1.test-utils.vuejs.org/api/#ref) +- [VueJs - template refs](https://vuejs.org/guide/essentials/template-refs.html) +- [VueTestUtils - ref](https://v1.test-utils.vuejs.org/api/#ref) ### Setup-methods @@ -170,21 +170,21 @@ Separate your setup from your actual tests: If you need a more complex setup to Use the trigger()-method to simulate a events [Testing Key, Mouse and other DOM events](https://v1.test-utils.vuejs.org/guides/#testing-key-mouse-and-other-dom-events) -- **Mouse-Click**: [VueTestUtils - trigger events](https://v1.test-utils.vuejs.org/guides/#trigger-events) -- **Keyboard-Input**: [VueTestUtils - keyboard example](https://v1.test-utils.vuejs.org/guides/#keyboard-example) -- **Drag & Drop**: trigger the events (e.g. dragstart, drop) and check for emitted events as reaction to that -- **Event from a child component**: [VueTestUtils - emitting from child component](https://v1.test-utils.vuejs.org/guides/#emitting-event-from-child-component) +- **Mouse-Click**: [VueTestUtils - trigger events](https://v1.test-utils.vuejs.org/guides/#trigger-events) +- **Keyboard-Input**: [VueTestUtils - keyboard example](https://v1.test-utils.vuejs.org/guides/#keyboard-example) +- **Drag & Drop**: trigger the events (e.g. dragstart, drop) and check for emitted events as reaction to that +- **Event from a child component**: [VueTestUtils - emitting from child component](https://v1.test-utils.vuejs.org/guides/#emitting-event-from-child-component) ### Testing Asynchronous Behavior -You can test asynchronous behavior by using ***Vue.nextTick()***: +You can test asynchronous behavior by using **_Vue.nextTick()_**: ```TypeScript await Vue.nextTick(); ... ``` -OR by ***trigger***ing an effect and ***await***ing this effect to take place: +OR by **_trigger_**ing an effect and **_await_**ing this effect to take place: ```TypeScript const btnNext = wrapper.find(`[data-testid="dialog-next"]`); @@ -220,7 +220,7 @@ consoleErrorSpy.mockRestore(); ### Testing Composables -- [VueTestUtils - Testing composables](https://test-utils.vuejs.org/guide/advanced/reusability-composition.html#testing-composables) +- [VueTestUtils - Testing composables](https://test-utils.vuejs.org/guide/advanced/reusability-composition.html#testing-composables) ## Mocking @@ -255,8 +255,8 @@ See also here: [VueTestUtils mount - mocks and stubs are now in global](https:// ### Mocking injections -- [Vue.js - Mocking injections](https://v1.test-utils.vuejs.org/guides/#mocking-injections) -- [VueTestUtils - provide / inject](https://test-utils.vuejs.org/guide/advanced/reusability-composition.html#provide-inject) +- [Vue.js - Mocking injections](https://v1.test-utils.vuejs.org/guides/#mocking-injections) +- [VueTestUtils - provide / inject](https://test-utils.vuejs.org/guide/advanced/reusability-composition.html#provide-inject) ### Mocking Vuex-Store @@ -313,10 +313,6 @@ it("should set something", () => { }); ``` -### Mocking Pinia-Stores - -***{{ tbd }}** (when Pinia-Stores are enabled for the project)* - ### Mocking Composables Sometimes - if a composable is simple and does not create sideeffects - it is okay to use it in the tests and avoid mocking it. @@ -333,18 +329,59 @@ jest.spyOn(ourExampleComposable, "useExample").mockReturnValue({ ... ``` +### Mocking Pinia-Stores + +Using our internal function `mockedPinaStoreTyping(useExampleStore)` mocks the given store and returns a correctly typed mocked instance of this store. All actions of the store are replaced by jest.fn() mock-functions. + +All PiniaStores should be mocked using this functionality. + +For integration-tests we use the original stores. + +Example from RoomDetails.page.unit.ts: + +```TypeScript +import { mockedPiniaStoreTyping } from "@@/tests/test-utils"; + +... + +describe("..." ()=> { + const setup = (...) => { + ... + const roomDetailsStore = mockedPiniaStoreTyping(useRoomDetailsStore); + ... + return { + roomDetailsStore, + ... + }; + }; + + describe("...", () => { + it("should ...", async () => { + const { wrapper, roomDetailsStore, room } = setup(); + + ... + expect(roomDetailsStore.createBoard).toHaveBeenCalledWith( + room.id, + serverApi.BoardLayout.Columns, + "pages.roomDetails.board.defaultName" + ); + }); + }); +}); +``` + ## Components that are hard to test If you ever get into trouble to write good tests for your compents or code in general - this might be an indicator, that **maybe your code is not structured good enough**. Consider: -- spliting your component into smaller sub-components with a small API -- extracting functionality into one or mutliple composables -- using an existing composable (from VueUse or an existing one in the project) -- using an existing vuetify-component instead of writing it all yourself -- reshaping the communication workflow (parameters, events, inject/provide, stores, composables) -- (replacing a Vuex-store with a Pinia-store) +- spliting your component into smaller sub-components with a small API +- extracting functionality into one or mutliple composables +- using an existing composable (from VueUse or an existing one in the project) +- using an existing vuetify-component instead of writing it all yourself +- reshaping the communication workflow (parameters, events, inject/provide, stores, composables) +- (replacing a Vuex-store with a Pinia-store) For more details on how to write good components and how to split your components: have a look at this great article of Olli: (tbd) From 7db002aaa92a5559830a9b01360cad109ca98837 Mon Sep 17 00:00:00 2001 From: "hoeppner.dataport" Date: Wed, 26 Mar 2025 09:11:45 +0100 Subject: [PATCH 2/3] chore: fix formating --- docs/nuxt-client/5_WritingTests.md | 88 +++++++++++++++--------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/docs/nuxt-client/5_WritingTests.md b/docs/nuxt-client/5_WritingTests.md index f0a7d53e..5ce25162 100644 --- a/docs/nuxt-client/5_WritingTests.md +++ b/docs/nuxt-client/5_WritingTests.md @@ -10,13 +10,13 @@ How to write valuable, reliable tests, that are easy to maintain. Writing good tests that cover all aspects of your code, leads to: -- **confidence**: to refactor your code -- **higher code quality**: as you review your code and identify problems when writing tests -- **well documented code**: as your tests describe how your code works +- **confidence**: to refactor your code +- **higher code quality**: as you review your code and identify problems when writing tests +- **well documented code**: as your tests describe how your code works and by that to: -- **developer happiness** :-) +- **developer happiness** :-) ### Unit-Tests vs. Component-Tests @@ -32,14 +32,14 @@ The enable us to **refactor** the internals of our components later on. ### Positive & negative Tests -- **positive tests** test the default cases of your code = **how it should work** -- **negative tests** test **error-cases** or **exception**-behaviour -- you need to write both to ensure your component works correctly -- think of edge-cases that might break your component e.g. when providing input to the component: - - **numbers**: high numbers, negative numbers, float<->integer, at the edge of a range that is expected... - - **dates**: none existing dates e.g. 30th February 2023, far away future,... - - **strings**: umlauts, url-special-characters (?, &, =, \/\/: ), very long strings for names, long strings without linebreaks - - **totally incorrect data**: e.g. giving a string instead of a number +- **positive tests** test the default cases of your code = **how it should work** +- **negative tests** test **error-cases** or **exception**-behaviour +- you need to write both to ensure your component works correctly +- think of edge-cases that might break your component e.g. when providing input to the component: + - **numbers**: high numbers, negative numbers, float<->integer, at the edge of a range that is expected... + - **dates**: none existing dates e.g. 30th February 2023, far away future,... + - **strings**: umlauts, url-special-characters (?, &, =, \/\/: ), very long strings for names, long strings without linebreaks + - **totally incorrect data**: e.g. giving a string instead of a number ### Use Vue-Test-Utils @@ -47,17 +47,17 @@ For testing our Vue-Components we use the **Vue Test Utils**. Vue Test Utils is Some functionality it provides: -- **mount()**: create a wrapper around the component and instantiate it -- **shallowMount()**: create a shallow wrapper of the component being tested with childcomponents being mocked -- **setMethods()**: mock function on the component -- **setProps()**: set a specific set of props on the component -- **findComponent()**: finds a component by it's class, name or ref -- **findAllComponents()**: finds all components by it's class, name or ref -- **[find() / findAll()](https://v1.test-utils.vuejs.org/api/wrapper/#find)**: search for html elements using html-selectors - - **deprecated for finding Components** - - use findComponent() or findAllComponents() instead -- **setData()**: set specific data on the component -- **trigger()** + **emit()**: test events and the flow of data +- **mount()**: create a wrapper around the component and instantiate it +- **shallowMount()**: create a shallow wrapper of the component being tested with childcomponents being mocked +- **setMethods()**: mock function on the component +- **setProps()**: set a specific set of props on the component +- **findComponent()**: finds a component by it's class, name or ref +- **findAllComponents()**: finds all components by it's class, name or ref +- **[find() / findAll()](https://v1.test-utils.vuejs.org/api/wrapper/#find)**: search for html elements using html-selectors + - **deprecated for finding Components** + - use findComponent() or findAllComponents() instead +- **setData()**: set specific data on the component +- **trigger()** + **emit()**: test events and the flow of data We think the **Vue Test Utils-documentation** is a valuable resource for learning how to test Vue-Components and a very good starting point on how to test certain aspects of your component. Please have a look at [https://test-utils.vuejs.org/guide](https://test-utils.vuejs.org/guide) @@ -140,8 +140,8 @@ We decided to unify the way data-testid's should be named in Frontend Arch Group Please use `
` in your HTML-code if you want to define a data-testid. -- do not use uppercase-characters -- only use one dash - right after data +- do not use uppercase-characters +- only use one dash - right after data You can later on check this using: @@ -156,8 +156,8 @@ expect( We also recommend to use refs instead of data-testids. But if you do that you ensure not to remove them once they are in the code... as they can be used in the component-code and for testing: -- [VueJs - template refs](https://vuejs.org/guide/essentials/template-refs.html) -- [VueTestUtils - ref](https://v1.test-utils.vuejs.org/api/#ref) +- [VueJs - template refs](https://vuejs.org/guide/essentials/template-refs.html) +- [VueTestUtils - ref](https://v1.test-utils.vuejs.org/api/#ref) ### Setup-methods @@ -170,10 +170,10 @@ Separate your setup from your actual tests: If you need a more complex setup to Use the trigger()-method to simulate a events [Testing Key, Mouse and other DOM events](https://v1.test-utils.vuejs.org/guides/#testing-key-mouse-and-other-dom-events) -- **Mouse-Click**: [VueTestUtils - trigger events](https://v1.test-utils.vuejs.org/guides/#trigger-events) -- **Keyboard-Input**: [VueTestUtils - keyboard example](https://v1.test-utils.vuejs.org/guides/#keyboard-example) -- **Drag & Drop**: trigger the events (e.g. dragstart, drop) and check for emitted events as reaction to that -- **Event from a child component**: [VueTestUtils - emitting from child component](https://v1.test-utils.vuejs.org/guides/#emitting-event-from-child-component) +- **Mouse-Click**: [VueTestUtils - trigger events](https://v1.test-utils.vuejs.org/guides/#trigger-events) +- **Keyboard-Input**: [VueTestUtils - keyboard example](https://v1.test-utils.vuejs.org/guides/#keyboard-example) +- **Drag & Drop**: trigger the events (e.g. dragstart, drop) and check for emitted events as reaction to that +- **Event from a child component**: [VueTestUtils - emitting from child component](https://v1.test-utils.vuejs.org/guides/#emitting-event-from-child-component) ### Testing Asynchronous Behavior @@ -220,7 +220,7 @@ consoleErrorSpy.mockRestore(); ### Testing Composables -- [VueTestUtils - Testing composables](https://test-utils.vuejs.org/guide/advanced/reusability-composition.html#testing-composables) +- [VueTestUtils - Testing composables](https://test-utils.vuejs.org/guide/advanced/reusability-composition.html#testing-composables) ## Mocking @@ -255,8 +255,8 @@ See also here: [VueTestUtils mount - mocks and stubs are now in global](https:// ### Mocking injections -- [Vue.js - Mocking injections](https://v1.test-utils.vuejs.org/guides/#mocking-injections) -- [VueTestUtils - provide / inject](https://test-utils.vuejs.org/guide/advanced/reusability-composition.html#provide-inject) +- [Vue.js - Mocking injections](https://v1.test-utils.vuejs.org/guides/#mocking-injections) +- [VueTestUtils - provide / inject](https://test-utils.vuejs.org/guide/advanced/reusability-composition.html#provide-inject) ### Mocking Vuex-Store @@ -346,13 +346,13 @@ import { mockedPiniaStoreTyping } from "@@/tests/test-utils"; describe("..." ()=> { const setup = (...) => { - ... + ... const roomDetailsStore = mockedPiniaStoreTyping(useRoomDetailsStore); ... - return { - roomDetailsStore, + return { + roomDetailsStore, ... - }; + }; }; describe("...", () => { @@ -376,12 +376,12 @@ If you ever get into trouble to write good tests for your compents or code in ge Consider: -- spliting your component into smaller sub-components with a small API -- extracting functionality into one or mutliple composables -- using an existing composable (from VueUse or an existing one in the project) -- using an existing vuetify-component instead of writing it all yourself -- reshaping the communication workflow (parameters, events, inject/provide, stores, composables) -- (replacing a Vuex-store with a Pinia-store) +- spliting your component into smaller sub-components with a small API +- extracting functionality into one or mutliple composables +- using an existing composable (from VueUse or an existing one in the project) +- using an existing vuetify-component instead of writing it all yourself +- reshaping the communication workflow (parameters, events, inject/provide, stores, composables) +- (replacing a Vuex-store with a Pinia-store) For more details on how to write good components and how to split your components: have a look at this great article of Olli: (tbd) From 18f303f593c808ce7f594b60f388a3ecc7381bd1 Mon Sep 17 00:00:00 2001 From: "hoeppner.dataport" Date: Wed, 26 Mar 2025 09:14:05 +0100 Subject: [PATCH 3/3] chore: undo formating changes --- docs/nuxt-client/5_WritingTests.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/nuxt-client/5_WritingTests.md b/docs/nuxt-client/5_WritingTests.md index 5ce25162..a218eae6 100644 --- a/docs/nuxt-client/5_WritingTests.md +++ b/docs/nuxt-client/5_WritingTests.md @@ -81,7 +81,7 @@ Especially in large test-files it is very helpful for the reader to have a tree- 1. describe block that contains the filename in the root-level of the test-file 2. sub-describe-blocks for groups of tests focussing the same aspects of your code -_Example:_ +*Example:* ```TypeScript describe('@components/share/ImportModal', () => { @@ -113,13 +113,13 @@ describe('@components/something/AddButton', () => { }); ``` -**Hint**: _maybe you should extract functionality from your component if this is needed e.g. to find a certain test in your file_ +**Hint**: *maybe you should extract functionality from your component if this is needed e.g. to find a certain test in your file* ### Name the test like a sentence "it should..." There is a reason we use the it-alias for writing our code and not the test-method: we want to describe the aspect that is tested in a natural sentence. That's why it is best practice to start your test with: it('should ...'); -_Example:_ +*Example:* ```TypeScript Bad: @@ -177,14 +177,14 @@ Use the trigger()-method to simulate a events ### Testing Asynchronous Behavior -You can test asynchronous behavior by using **_Vue.nextTick()_**: +You can test asynchronous behavior by using ***Vue.nextTick()***: ```TypeScript await Vue.nextTick(); ... ``` -OR by **_trigger_**ing an effect and **_await_**ing this effect to take place: +OR by ***trigger***ing an effect and ***await***ing this effect to take place: ```TypeScript const btnNext = wrapper.find(`[data-testid="dialog-next"]`);