diff --git a/packages/utility-features/test-utils/index.ts b/packages/utility-features/test-utils/index.ts
index ed5afc6535..aca8fd5f9a 100644
--- a/packages/utility-features/test-utils/index.ts
+++ b/packages/utility-features/test-utils/index.ts
@@ -2,3 +2,5 @@ export * from "./src/flush-promises";
export * from "./src/get-global-override-for-function";
export * from "./src/get-global-override";
export * from "./src/get-promise-status";
+export * from "./src/render-for";
+export * from "./src/run-with-thrown-mobx-reactions";
diff --git a/packages/utility-features/test-utils/package.json b/packages/utility-features/test-utils/package.json
index 3adf142d0c..0dc8df9e6f 100644
--- a/packages/utility-features/test-utils/package.json
+++ b/packages/utility-features/test-utils/package.json
@@ -23,5 +23,13 @@
"build": "webpack",
"dev": "webpack --mode=development --watch",
"test": "jest --coverage --runInBand"
+ },
+
+ "peerDependencies": {
+ "@ogre-tools/injectable": "^15.1.2",
+ "@ogre-tools/injectable-react": "^15.1.2",
+ "@testing-library/react": "^12.1.5",
+ "lodash": "^4.17.21",
+ "react": "^17.0.2"
}
}
diff --git a/packages/utility-features/test-utils/src/render-for.tsx b/packages/utility-features/test-utils/src/render-for.tsx
new file mode 100644
index 0000000000..2508d70d69
--- /dev/null
+++ b/packages/utility-features/test-utils/src/render-for.tsx
@@ -0,0 +1,24 @@
+import React from "react";
+import type { RenderResult } from "@testing-library/react";
+import { render as testingLibraryRender } from "@testing-library/react";
+import type { DiContainer } from "@ogre-tools/injectable";
+import { DiContextProvider } from "@ogre-tools/injectable-react";
+
+export type DiRender = (ui: React.ReactElement) => RenderResult;
+
+type DiRenderFor = (di: DiContainer) => DiRender;
+
+export const renderFor: DiRenderFor = (di) => (ui) => {
+ const result = testingLibraryRender(
+ {ui}
+ );
+
+ return {
+ ...result,
+
+ rerender: (ui: React.ReactElement) =>
+ result.rerender(
+ {ui}
+ ),
+ };
+};
diff --git a/packages/utility-features/test-utils/src/run-with-thrown-mobx-reactions.ts b/packages/utility-features/test-utils/src/run-with-thrown-mobx-reactions.ts
new file mode 100644
index 0000000000..cc3f7ada23
--- /dev/null
+++ b/packages/utility-features/test-utils/src/run-with-thrown-mobx-reactions.ts
@@ -0,0 +1,38 @@
+import { noop } from "lodash/fp";
+import { _resetGlobalState, configure } from "mobx";
+
+export const runWithThrownMobxReactions = (callback: () => void) => {
+ const originalConsoleWarn = console.warn;
+
+ console.warn = noop;
+
+ configure({
+ disableErrorBoundaries: true,
+ });
+
+ console.warn = originalConsoleWarn;
+
+ let error: any;
+
+ try {
+ callback();
+ } catch (e) {
+ error = e;
+ } finally {
+ configure({
+ disableErrorBoundaries: false,
+ });
+
+ // This is because when disableErrorBoundaries is true, MobX doesn't recover from the thrown
+ // errors, and its global state starts bleeding between tests making.
+ _resetGlobalState();
+
+ if (!error) {
+ throw new Error(
+ "Tried to run with thrown MobX reactions but nothing was thrown"
+ );
+ } else {
+ throw error;
+ }
+ }
+};