diff --git a/ui/package.json b/ui/package.json
index c3f3e58..e4ff9a9 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -61,8 +61,8 @@
     "lint": "npm run lint:types & npm run lint:eslint",
     "lint:eslint": "eslint ./src --max-warnings 0 --cache --config .eslintrc.js",
     "lint:types": "tsc --noemit",
-    "jest": "craco test",
-    "jest:ci": "CI=true craco test --verbose --ci --coverage",
+    "jest": "craco test --setupFilesAfterEnv ./testing/setupTests.ts",
+    "jest:ci": "CI=true craco test --setupFilesAfterEnv ./testing/setupTests.ts --verbose --ci --coverage",
     "genSchemaTypes": "npx apollo client:codegen --target=typescript --globalTypesFile=src/__generated__/globalTypes.ts",
     "extractTranslations": "i18next -c i18next-parser.config.js",
     "prepare": "(cd .. && npx husky install)"
diff --git a/ui/src/Pages/PeoplePage/FaceCircleImage.test.tsx b/ui/src/Pages/PeoplePage/FaceCircleImage.test.tsx
index d82ffc1..f0a23a1 100644
--- a/ui/src/Pages/PeoplePage/FaceCircleImage.test.tsx
+++ b/ui/src/Pages/PeoplePage/FaceCircleImage.test.tsx
@@ -1,12 +1,8 @@
-import '@testing-library/jest-dom'
-
 import React from 'react'
 import { render, screen } from '@testing-library/react'
 import FaceCircleImage from './FaceCircleImage'
 import { myFaces_myFaceGroups_imageFaces } from './__generated__/myFaces'
 
-require('../../localization').setupLocalization()
-
 test('face circle image', () => {
   const imageFace: myFaces_myFaceGroups_imageFaces = {
     __typename: 'ImageFace',
diff --git a/ui/src/Pages/PeoplePage/PeoplePage.test.tsx b/ui/src/Pages/PeoplePage/PeoplePage.test.tsx
index 4480f4f..3a6e9b3 100644
--- a/ui/src/Pages/PeoplePage/PeoplePage.test.tsx
+++ b/ui/src/Pages/PeoplePage/PeoplePage.test.tsx
@@ -1,5 +1,3 @@
-import '@testing-library/jest-dom'
-
 import React from 'react'
 import { fireEvent, render, screen, waitFor } from '@testing-library/react'
 import PeoplePage, {
@@ -11,8 +9,6 @@ import { MockedProvider } from '@apollo/client/testing'
 import { MemoryRouter } from 'react-router'
 import { myFaces_myFaceGroups } from './__generated__/myFaces'
 
-require('../../localization').setupLocalization()
-
 jest.mock('../../hooks/useScrollPagination')
 
 describe('PeoplePage component', () => {
diff --git a/ui/src/Pages/PeoplePage/SingleFaceGroup/SingleFaceGroup.test.tsx b/ui/src/Pages/PeoplePage/SingleFaceGroup/SingleFaceGroup.test.tsx
index a0bc0df..acd0e20 100644
--- a/ui/src/Pages/PeoplePage/SingleFaceGroup/SingleFaceGroup.test.tsx
+++ b/ui/src/Pages/PeoplePage/SingleFaceGroup/SingleFaceGroup.test.tsx
@@ -1,12 +1,8 @@
-import '@testing-library/jest-dom'
-
 import React from 'react'
 import { render, screen, waitFor } from '@testing-library/react'
 import { MockedProvider } from '@apollo/client/testing'
 import SingleFaceGroup, { SINGLE_FACE_GROUP } from './SingleFaceGroup'
 
-require('../../../localization').setupLocalization()
-
 jest.mock('../../../hooks/useScrollPagination')
 
 test('single face group', async () => {
diff --git a/ui/src/Pages/SettingsPage/Users/AddUserRow.test.tsx b/ui/src/Pages/SettingsPage/Users/AddUserRow.test.tsx
new file mode 100644
index 0000000..eadd643
--- /dev/null
+++ b/ui/src/Pages/SettingsPage/Users/AddUserRow.test.tsx
@@ -0,0 +1,93 @@
+import React from 'react'
+import { fireEvent, render, screen, waitFor } from '@testing-library/react'
+import AddUserRow, {
+  CREATE_USER_MUTATION,
+  USER_ADD_ROOT_PATH_MUTATION,
+} from './AddUserRow'
+import { MockedProvider } from '@apollo/client/testing'
+
+const gqlMock = [
+  {
+    request: {
+      query: CREATE_USER_MUTATION,
+      variables: { username: 'testuser', admin: false },
+    },
+    result: {
+      data: {
+        createUser: {
+          id: '123',
+          username: 'testuser',
+          admin: false,
+          __typename: 'User',
+        },
+      },
+    },
+  },
+  {
+    request: {
+      query: USER_ADD_ROOT_PATH_MUTATION,
+      variables: { id: '123', rootPath: '/tmp' },
+    },
+    result: { data: { userAddRootPath: { id: '567', __typename: 'Album' } } },
+  },
+]
+
+test('Add user with username and path', async () => {
+  const userAdded = jest.fn()
+  const setShow = jest.fn()
+
+  render(
+    <MockedProvider addTypename={true} mocks={gqlMock}>
+      <table>
+        <tbody>
+          <AddUserRow onUserAdded={userAdded} setShow={setShow} show={true} />
+        </tbody>
+      </table>
+    </MockedProvider>
+  )
+
+  const usernameInput = screen.getByPlaceholderText('Username')
+  const pathInput = screen.getByPlaceholderText('/path/to/photos')
+  const addUserBtn = screen.getByText('Add user')
+
+  fireEvent.change(usernameInput, { target: { value: 'testuser' } })
+  fireEvent.change(pathInput, { target: { value: '/tmp' } })
+  fireEvent.click(addUserBtn)
+
+  await waitFor(() => {
+    expect(userAdded).toHaveBeenCalledTimes(1)
+  })
+
+  expect(setShow).not.toHaveBeenCalled()
+})
+
+test('Add user with only username', async () => {
+  const userAdded = jest.fn()
+  const setShow = jest.fn()
+
+  render(
+    <MockedProvider addTypename={true} mocks={gqlMock}>
+      <table>
+        <tbody>
+          <AddUserRow onUserAdded={userAdded} setShow={setShow} show={true} />
+        </tbody>
+      </table>
+    </MockedProvider>
+  )
+
+  const usernameInput = screen.getByPlaceholderText('Username')
+  const addUserBtn = screen.getByText('Add user')
+
+  // don't set path
+  // const pathInput = screen.getByPlaceholderText('/path/to/photos')
+  // fireEvent.change(pathInput, { target: { value: '/tmp' } })
+
+  fireEvent.change(usernameInput, { target: { value: 'testuser' } })
+  fireEvent.click(addUserBtn)
+
+  await waitFor(() => {
+    expect(userAdded).toHaveBeenCalledTimes(1)
+  })
+
+  expect(setShow).not.toHaveBeenCalled()
+})
diff --git a/ui/src/Pages/SettingsPage/Users/AddUserRow.tsx b/ui/src/Pages/SettingsPage/Users/AddUserRow.tsx
index b8c28b3..4ff9036 100644
--- a/ui/src/Pages/SettingsPage/Users/AddUserRow.tsx
+++ b/ui/src/Pages/SettingsPage/Users/AddUserRow.tsx
@@ -5,7 +5,7 @@ import Checkbox from '../../../primitives/form/Checkbox'
 import { TextField, Button, ButtonGroup } from '../../../primitives/form/Input'
 import { TableRow, TableCell } from '../../../primitives/Table'
 
-const CREATE_USER_MUTATION = gql`
+export const CREATE_USER_MUTATION = gql`
   mutation createUser($username: String!, $admin: Boolean!) {
     createUser(username: $username, admin: $admin) {
       id
@@ -41,16 +41,19 @@ const AddUserRow = ({ setShow, show, onUserAdded }: AddUserRowProps) => {
   const { t } = useTranslation()
   const [state, setState] = useState(initialState)
 
+  const finished = () => {
+    setState(initialState)
+    onUserAdded()
+  }
+
   const [addRootPath, { loading: addRootPathLoading }] = useMutation(
     USER_ADD_ROOT_PATH_MUTATION,
     {
       onCompleted: () => {
-        setState(initialState)
-        onUserAdded()
+        finished()
       },
       onError: () => {
-        setState(initialState)
-        onUserAdded()
+        finished()
       },
     }
   )
@@ -67,7 +70,7 @@ const AddUserRow = ({ setShow, show, onUserAdded }: AddUserRowProps) => {
             },
           })
         } else {
-          setState(initialState)
+          finished()
         }
       },
     }
diff --git a/ui/src/Pages/SharePage/SharePage.test.js b/ui/src/Pages/SharePage/SharePage.test.js
index 1dbf8d9..cae0ec7 100644
--- a/ui/src/Pages/SharePage/SharePage.test.js
+++ b/ui/src/Pages/SharePage/SharePage.test.js
@@ -1,5 +1,3 @@
-import '@testing-library/jest-dom'
-
 import React from 'react'
 import { MemoryRouter } from 'react-router-dom'
 import { MockedProvider } from '@apollo/client/testing'
@@ -18,8 +16,6 @@ import SharePage, {
 import { SIDEBAR_DOWNLOAD_QUERY } from '../../components/sidebar/SidebarDownload'
 import { SHARE_ALBUM_QUERY } from './AlbumSharePage'
 
-require('../../localization').setupLocalization()
-
 describe('load correct share page, based on graphql query', () => {
   const token = 'TOKEN123'
 
diff --git a/ui/src/components/layout/Layout.test.js b/ui/src/components/layout/Layout.test.js
index 1140b86..b053203 100644
--- a/ui/src/components/layout/Layout.test.js
+++ b/ui/src/components/layout/Layout.test.js
@@ -1,10 +1,7 @@
-import '@testing-library/jest-dom'
 import { render, screen } from '@testing-library/react'
 import React from 'react'
 import Layout from './Layout'
 
-require('../../localization').setupLocalization()
-
 test('Layout component', async () => {
   render(
     <Layout>
diff --git a/ui/src/components/layout/MainMenu.test.tsx b/ui/src/components/layout/MainMenu.test.tsx
index dbb5fb1..89623ea 100644
--- a/ui/src/components/layout/MainMenu.test.tsx
+++ b/ui/src/components/layout/MainMenu.test.tsx
@@ -1,5 +1,3 @@
-import '@testing-library/jest-dom'
-
 import React from 'react'
 import { MockedProvider } from '@apollo/client/testing'
 import { render, screen } from '@testing-library/react'
@@ -9,8 +7,6 @@ import { ADMIN_QUERY } from './Layout'
 import { MemoryRouter } from 'react-router-dom'
 import MainMenu, { MAPBOX_QUERY } from './MainMenu'
 
-require('../../localization').setupLocalization()
-
 jest.mock('../../helpers/authentication.ts')
 
 const authTokenMock = authentication.authToken as jest.MockedFunction<
diff --git a/ui/src/components/photoGallery/PhotoGallery.test.tsx b/ui/src/components/photoGallery/PhotoGallery.test.tsx
index 18fcd55..8fe4aa0 100644
--- a/ui/src/components/photoGallery/PhotoGallery.test.tsx
+++ b/ui/src/components/photoGallery/PhotoGallery.test.tsx
@@ -1,4 +1,3 @@
-import '@testing-library/jest-dom'
 import { render, screen } from '@testing-library/react'
 
 import React from 'react'
@@ -6,8 +5,6 @@ import { MediaType } from '../../__generated__/globalTypes'
 import PhotoGallery from './PhotoGallery'
 import { PhotoGalleryState } from './photoGalleryReducer'
 
-require('../../localization').setupLocalization()
-
 jest.mock('./photoGalleryMutations', () => ({
   useMarkFavoriteMutation: () => [jest.fn()],
 }))
diff --git a/ui/src/components/photoGallery/presentView/PresentMedia.test.tsx b/ui/src/components/photoGallery/presentView/PresentMedia.test.tsx
index 027b5fc..06c3423 100644
--- a/ui/src/components/photoGallery/presentView/PresentMedia.test.tsx
+++ b/ui/src/components/photoGallery/presentView/PresentMedia.test.tsx
@@ -1,4 +1,3 @@
-import '@testing-library/jest-dom'
 import { render, screen } from '@testing-library/react'
 
 import React from 'react'
diff --git a/ui/src/components/photoGallery/presentView/PresentNavigationOverlay.test.tsx b/ui/src/components/photoGallery/presentView/PresentNavigationOverlay.test.tsx
index 89dd1fb..c9561f0 100644
--- a/ui/src/components/photoGallery/presentView/PresentNavigationOverlay.test.tsx
+++ b/ui/src/components/photoGallery/presentView/PresentNavigationOverlay.test.tsx
@@ -1,6 +1,3 @@
-import '@testing-library/jest-dom'
-import '@testing-library/user-event'
-
 import React from 'react'
 import PresentNavigationOverlay from './PresentNavigationOverlay'
 import { fireEvent, render, screen, act } from '@testing-library/react'
diff --git a/ui/src/components/routes/AuthorizedRoute.test.js b/ui/src/components/routes/AuthorizedRoute.test.js
index 50fbf7c..7bd8371 100644
--- a/ui/src/components/routes/AuthorizedRoute.test.js
+++ b/ui/src/components/routes/AuthorizedRoute.test.js
@@ -1,5 +1,3 @@
-import '@testing-library/jest-dom'
-
 import React from 'react'
 import AuthorizedRoute from './AuthorizedRoute'
 import { render, screen } from '@testing-library/react'
diff --git a/ui/src/components/routes/Routes.test.js b/ui/src/components/routes/Routes.test.js
index c29bbc1..1c7c44e 100644
--- a/ui/src/components/routes/Routes.test.js
+++ b/ui/src/components/routes/Routes.test.js
@@ -1,5 +1,3 @@
-import '@testing-library/jest-dom'
-
 import React from 'react'
 
 import Routes from './Routes'
@@ -14,8 +12,6 @@ jest.mock('../../Pages/LoginPage/LoginPage.tsx', () => () => (
   <div>mocked login page</div>
 ))
 
-require('../../localization').setupLocalization()
-
 describe('routes', () => {
   test('unauthorized root path should navigate to login page', async () => {
     render(
diff --git a/ui/src/components/sidebar/MediaSidebar.test.js b/ui/src/components/sidebar/MediaSidebar.test.js
index d5ca1a7..7ec5f15 100644
--- a/ui/src/components/sidebar/MediaSidebar.test.js
+++ b/ui/src/components/sidebar/MediaSidebar.test.js
@@ -1,11 +1,7 @@
-import '@testing-library/jest-dom'
-
 import React from 'react'
 import { render, screen } from '@testing-library/react'
 import { MetadataInfo } from './MediaSidebar'
 
-require('../../localization').setupLocalization()
-
 describe('MetadataInfo', () => {
   test('without EXIF information', async () => {
     const media = {
diff --git a/ui/testing/setupTests.ts b/ui/testing/setupTests.ts
new file mode 100644
index 0000000..99c2dbf
--- /dev/null
+++ b/ui/testing/setupTests.ts
@@ -0,0 +1,9 @@
+// jest-dom adds custom jest matchers for asserting on DOM nodes.
+// allows you to do things like:
+// expect(element).toHaveTextContent(/react/i)
+// learn more: https://github.com/testing-library/jest-dom
+import '@testing-library/jest-dom'
+import '@testing-library/user-event'
+
+// setup localization to make it easier to select elements by text
+require('../src/localization').setupLocalization()
