From 06ef8515cb9fd44186c7ee661a12e029e5cda35f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9o=20LUDWIG?= <contact@theoludwig.fr>
Date: Thu, 2 May 2024 23:48:47 +0200
Subject: [PATCH] refactor: mocks data for tests

---
 domain/entities/HabitHistory.ts               |   4 +-
 .../entities/__tests__/HabitsTracker.test.ts  | 106 ++++++++++++++++
 infrastructure/instances.ts                   |  12 +-
 .../data-transfer-objects/HabitDTO.ts         |  79 ++++++++++++
 .../data-transfer-objects/HabitProgressDTO.ts |  78 ++++++++++++
 .../__tests__/HabitDTO.test.ts                | 100 +++++++++++++++
 .../__tests__/HabitProgressDTO.test.ts        |  22 ++++
 .../supabase/repositories/Authentication.ts   |   4 +-
 .../repositories/GetHabitProgressHistory.ts   |  45 ++-----
 .../repositories/GetHabitsByUserId.ts         |  41 +------
 .../supabase/repositories/HabitCreate.ts      |  40 ++----
 .../supabase/repositories/HabitEdit.ts        |  51 ++------
 .../repositories/HabitProgressCreate.ts       |  39 +++---
 .../repositories/HabitProgressUpdate.ts       |  40 +++---
 infrastructure/supabase/supabase.ts           |  10 ++
 jest.config.json                              |   6 +
 presentation/presenters/_Presenter.ts         |   5 +-
 .../HabitForm/IconSelectorModal.tsx           |   4 +-
 .../components/HabitsMainPage/HabitCard.tsx   |   6 +-
 .../components/HabitsMainPage/HabitsList.tsx  |   2 +-
 .../react/contexts/Authentication.tsx         |   4 +-
 presentation/react/contexts/HabitsTracker.tsx |   2 +-
 .../hooks/__tests__/usePresenterState.test.ts |   2 +-
 tests/mocks/domain/Habit.ts                   | 115 ++++++++++++++++++
 tests/mocks/domain/HabitProgress.ts           |  51 ++++++++
 tests/mocks/domain/User.ts                    |  30 +++++
 tests/mocks/supabase/Habit.ts                 |  79 ++++++++++++
 tests/mocks/supabase/HabitProgress.ts         |  49 ++++++++
 tests/mocks/supabase/User.ts                  |  63 ++++++++++
 29 files changed, 875 insertions(+), 214 deletions(-)
 create mode 100644 domain/entities/__tests__/HabitsTracker.test.ts
 create mode 100644 infrastructure/supabase/data-transfer-objects/HabitDTO.ts
 create mode 100644 infrastructure/supabase/data-transfer-objects/HabitProgressDTO.ts
 create mode 100644 infrastructure/supabase/data-transfer-objects/__tests__/HabitDTO.test.ts
 create mode 100644 infrastructure/supabase/data-transfer-objects/__tests__/HabitProgressDTO.test.ts
 create mode 100644 tests/mocks/domain/Habit.ts
 create mode 100644 tests/mocks/domain/HabitProgress.ts
 create mode 100644 tests/mocks/domain/User.ts
 create mode 100644 tests/mocks/supabase/Habit.ts
 create mode 100644 tests/mocks/supabase/HabitProgress.ts
 create mode 100644 tests/mocks/supabase/User.ts

diff --git a/domain/entities/HabitHistory.ts b/domain/entities/HabitHistory.ts
index 5f34f3e..66ecddb 100644
--- a/domain/entities/HabitHistory.ts
+++ b/domain/entities/HabitHistory.ts
@@ -1,8 +1,8 @@
 import { getISODate, getWeekNumber } from "@/utils/dates"
-import type { Habit } from "./Habit"
-import type { HabitProgress } from "./HabitProgress"
 import type { GoalProgress } from "./Goal"
 import { GoalBooleanProgress, GoalNumericProgress } from "./Goal"
+import type { Habit } from "./Habit"
+import type { HabitProgress } from "./HabitProgress"
 
 export interface HabitHistoryJSON {
   habit: Habit
diff --git a/domain/entities/__tests__/HabitsTracker.test.ts b/domain/entities/__tests__/HabitsTracker.test.ts
new file mode 100644
index 0000000..3b5f3cb
--- /dev/null
+++ b/domain/entities/__tests__/HabitsTracker.test.ts
@@ -0,0 +1,106 @@
+import { HABIT_MOCK } from "@/tests/mocks/domain/Habit"
+import { GOAL_FREQUENCIES } from "../Goal"
+import { HabitsTracker } from "../HabitsTracker"
+import { HabitHistory } from "../HabitHistory"
+import { HABIT_PROGRESS_MOCK } from "@/tests/mocks/domain/HabitProgress"
+
+describe("domain/entities/HabitsTracker", () => {
+  describe("HabitsTracker.default", () => {
+    for (const frequency of GOAL_FREQUENCIES) {
+      it(`should return empty habitsHistory for ${frequency}`, () => {
+        const habitsTracker = HabitsTracker.default()
+        expect(habitsTracker.habitsHistory[frequency]).toEqual([])
+      })
+    }
+  })
+
+  describe("getAllHabitsHistory", () => {
+    it("should return all habits history", () => {
+      const habitsTracker = HabitsTracker.default()
+      const habit = HABIT_MOCK.examplesByNames.Walk
+      habitsTracker.addHabit(habit)
+      expect(habitsTracker.getAllHabitsHistory()).toEqual([
+        new HabitHistory({
+          habit,
+          progressHistory: [],
+        }),
+      ])
+    })
+
+    it("should return empty array when no habits are added", () => {
+      const habitsTracker = HabitsTracker.default()
+      expect(habitsTracker.getAllHabitsHistory()).toEqual([])
+    })
+  })
+
+  describe("getHabitHistoryById", () => {
+    it("should return habit history by id", () => {
+      const habitsTracker = HabitsTracker.default()
+      const habit = HABIT_MOCK.examplesByNames.Walk
+      habitsTracker.addHabit(habit)
+      expect(habitsTracker.getHabitHistoryById(habit.id)).toEqual(
+        new HabitHistory({
+          habit,
+          progressHistory: [],
+        }),
+      )
+    })
+
+    it("should return undefined when habit is not found", () => {
+      const habitsTracker = HabitsTracker.default()
+      expect(habitsTracker.getHabitHistoryById("invalid-id")).toBeUndefined()
+    })
+  })
+
+  describe("addHabit", () => {
+    it("should add habit to habitsHistory", () => {
+      const habitsTracker = HabitsTracker.default()
+      const habit = HABIT_MOCK.examplesByNames.Walk
+      habitsTracker.addHabit(habit)
+      expect(habitsTracker.habitsHistory[habit.goal.frequency]).toEqual([
+        new HabitHistory({
+          habit,
+          progressHistory: [],
+        }),
+      ])
+    })
+  })
+
+  describe("editHabit", () => {
+    it("should edit habit in habitsHistory", () => {
+      const habitsTracker = HabitsTracker.default()
+      const habit = HABIT_MOCK.examplesByNames.Walk
+      habitsTracker.addHabit(habit)
+      habit.name = "Run"
+      habitsTracker.editHabit(habit)
+      expect(habitsTracker.habitsHistory[habit.goal.frequency]).toEqual([
+        new HabitHistory({
+          habit,
+          progressHistory: [],
+        }),
+      ])
+    })
+
+    it("should not edit habit in habitsHistory when habit is not found", () => {
+      const habitsTracker = HabitsTracker.default()
+      const habit = HABIT_MOCK.examplesByNames.Walk
+      habitsTracker.editHabit(habit)
+      expect(habitsTracker.habitsHistory[habit.goal.frequency]).toEqual([])
+    })
+  })
+
+  describe("updateHabitProgress", () => {
+    it("should update habit progress in habitsHistory (add new habit progress if not yet added)", () => {
+      const habitsTracker = HabitsTracker.default()
+      const habit = HABIT_MOCK.examplesByNames["Clean the house"]
+      habitsTracker.addHabit(habit)
+      habitsTracker.updateHabitProgress(HABIT_PROGRESS_MOCK.exampleByIds[1])
+      expect(habitsTracker.habitsHistory[habit.goal.frequency]).toEqual([
+        new HabitHistory({
+          habit,
+          progressHistory: [HABIT_PROGRESS_MOCK.exampleByIds[1]],
+        }),
+      ])
+    })
+  })
+})
diff --git a/infrastructure/instances.ts b/infrastructure/instances.ts
index abf3fb7..1e01102 100644
--- a/infrastructure/instances.ts
+++ b/infrastructure/instances.ts
@@ -1,19 +1,19 @@
 import { AuthenticationUseCase } from "@/domain/use-cases/Authentication"
+import { HabitCreateUseCase } from "@/domain/use-cases/HabitCreate"
+import { HabitEditUseCase } from "@/domain/use-cases/HabitEdit"
+import { HabitGoalProgressUpdateUseCase } from "@/domain/use-cases/HabitGoalProgressUpdate"
+import { HabitStopUseCase } from "@/domain/use-cases/HabitStop"
+import { AuthenticationPresenter } from "@/presentation/presenters/Authentication"
 import { RetrieveHabitsTrackerUseCase } from "../domain/use-cases/RetrieveHabitsTracker"
 import { HabitsTrackerPresenter } from "../presentation/presenters/HabitsTracker"
 import { AuthenticationSupabaseRepository } from "./supabase/repositories/Authentication"
 import { GetHabitProgressHistorySupabaseRepository } from "./supabase/repositories/GetHabitProgressHistory"
 import { GetHabitsByUserIdSupabaseRepository } from "./supabase/repositories/GetHabitsByUserId"
-import { supabaseClient } from "./supabase/supabase"
-import { AuthenticationPresenter } from "@/presentation/presenters/Authentication"
 import { HabitCreateSupabaseRepository } from "./supabase/repositories/HabitCreate"
-import { HabitCreateUseCase } from "@/domain/use-cases/HabitCreate"
 import { HabitEditSupabaseRepository } from "./supabase/repositories/HabitEdit"
-import { HabitEditUseCase } from "@/domain/use-cases/HabitEdit"
 import { HabitProgressCreateSupabaseRepository } from "./supabase/repositories/HabitProgressCreate"
 import { HabitProgressUpdateSupabaseRepository } from "./supabase/repositories/HabitProgressUpdate"
-import { HabitGoalProgressUpdateUseCase } from "@/domain/use-cases/HabitGoalProgressUpdate"
-import { HabitStopUseCase } from "@/domain/use-cases/HabitStop"
+import { supabaseClient } from "./supabase/supabase"
 
 /**
  * Repositories
diff --git a/infrastructure/supabase/data-transfer-objects/HabitDTO.ts b/infrastructure/supabase/data-transfer-objects/HabitDTO.ts
new file mode 100644
index 0000000..b0b2af4
--- /dev/null
+++ b/infrastructure/supabase/data-transfer-objects/HabitDTO.ts
@@ -0,0 +1,79 @@
+import type { Goal } from "@/domain/entities/Goal"
+import { GoalBoolean, GoalNumeric } from "@/domain/entities/Goal"
+import type { HabitCreateData, HabitEditData } from "@/domain/entities/Habit"
+import { Habit } from "@/domain/entities/Habit"
+import type {
+  SupabaseHabit,
+  SupabaseHabitInsert,
+  SupabaseHabitUpdate,
+} from "../supabase"
+
+export const habitSupabaseDTO = {
+  fromSupabaseToDomain: (supabaseHabit: SupabaseHabit): Habit => {
+    let goal: Goal
+    if (
+      supabaseHabit.goal_target != null &&
+      supabaseHabit.goal_target_unit != null
+    ) {
+      goal = new GoalNumeric({
+        frequency: supabaseHabit.goal_frequency,
+        target: {
+          value: supabaseHabit.goal_target,
+          unit: supabaseHabit.goal_target_unit,
+        },
+      })
+    } else {
+      goal = new GoalBoolean({
+        frequency: supabaseHabit.goal_frequency,
+      })
+    }
+    const habit = new Habit({
+      id: supabaseHabit.id.toString(),
+      name: supabaseHabit.name,
+      color: supabaseHabit.color,
+      icon: supabaseHabit.icon,
+      userId: supabaseHabit.user_id.toString(),
+      startDate: new Date(supabaseHabit.start_date),
+      endDate:
+        supabaseHabit.end_date != null
+          ? new Date(supabaseHabit.end_date)
+          : undefined,
+      goal,
+    })
+    return habit
+  },
+  fromDomainCreateDataToSupabaseInsert: (
+    habitCreateData: HabitCreateData,
+  ): SupabaseHabitInsert => {
+    return {
+      name: habitCreateData.name,
+      color: habitCreateData.color,
+      icon: habitCreateData.icon,
+      goal_frequency: habitCreateData.goal.frequency,
+      ...(habitCreateData.goal.target.type === "numeric"
+        ? {
+            goal_target: habitCreateData.goal.target.value,
+            goal_target_unit: habitCreateData.goal.target.unit,
+          }
+        : {}),
+    }
+  },
+  fromDomainEditDataToSupabaseUpdate: (
+    habitEditData: HabitEditData,
+  ): SupabaseHabitUpdate => {
+    return {
+      name: habitEditData.name,
+      color: habitEditData.color,
+      icon: habitEditData.icon,
+      end_date: habitEditData?.endDate?.toISOString(),
+    }
+  },
+}
+
+export const habitsSupabaseDTO = {
+  fromSupabaseToDomain: (supabaseHabits: SupabaseHabit[]): Habit[] => {
+    return supabaseHabits.map((supabaseHabit) => {
+      return habitSupabaseDTO.fromSupabaseToDomain(supabaseHabit)
+    })
+  },
+}
diff --git a/infrastructure/supabase/data-transfer-objects/HabitProgressDTO.ts b/infrastructure/supabase/data-transfer-objects/HabitProgressDTO.ts
new file mode 100644
index 0000000..b4afdb3
--- /dev/null
+++ b/infrastructure/supabase/data-transfer-objects/HabitProgressDTO.ts
@@ -0,0 +1,78 @@
+import type { Goal, GoalProgress } from "@/domain/entities/Goal"
+import {
+  GoalBooleanProgress,
+  GoalNumericProgress,
+} from "@/domain/entities/Goal"
+import { HabitProgress } from "@/domain/entities/HabitProgress"
+import type { HabitProgressCreateOptions } from "@/domain/repositories/HabitProgressCreate"
+import type { HabitProgressUpdateOptions } from "@/domain/repositories/HabitProgressUpdate"
+import type {
+  SupabaseHabitProgress,
+  SupabaseHabitProgressInsert,
+  SupabaseHabitProgressUpdate,
+} from "../supabase"
+
+export const habitProgressSupabaseDTO = {
+  fromSupabaseToDomain: (
+    supabaseHabitProgress: SupabaseHabitProgress,
+    goal: Goal,
+  ): HabitProgress => {
+    let goalProgress: GoalProgress | null = null
+    if (goal.isNumeric()) {
+      goalProgress = new GoalNumericProgress({
+        goal,
+        progress: supabaseHabitProgress.goal_progress,
+      })
+    } else if (goal.isBoolean()) {
+      goalProgress = new GoalBooleanProgress({
+        goal,
+        progress: supabaseHabitProgress.goal_progress === 1,
+      })
+    }
+    const habitProgress = new HabitProgress({
+      id: supabaseHabitProgress.id.toString(),
+      habitId: supabaseHabitProgress.habit_id.toString(),
+      goalProgress: goalProgress as GoalProgress,
+      date: new Date(supabaseHabitProgress.date),
+    })
+    return habitProgress
+  },
+  fromDomainDataToSupabaseInsert: (
+    habitProgressData: HabitProgressCreateOptions["habitProgressData"],
+  ): SupabaseHabitProgressInsert => {
+    const { goalProgress, date, habitId } = habitProgressData
+    let goalProgressValue = goalProgress.isCompleted() ? 1 : 0
+    if (goalProgress.isNumeric()) {
+      goalProgressValue = goalProgress.progress
+    }
+    return {
+      habit_id: Number.parseInt(habitId, 10),
+      date: date.toISOString(),
+      goal_progress: goalProgressValue,
+    }
+  },
+  fromDomainDataToSupabaseUpdate: (
+    habitProgressData: HabitProgressUpdateOptions["habitProgressData"],
+  ): SupabaseHabitProgressUpdate => {
+    const { goalProgress, date } = habitProgressData
+    let goalProgressValue = goalProgress.isCompleted() ? 1 : 0
+    if (goalProgress.isNumeric()) {
+      goalProgressValue = goalProgress.progress
+    }
+    return {
+      date: date.toISOString(),
+      goal_progress: goalProgressValue,
+    }
+  },
+}
+
+export const habitProgressHistorySupabaseDTO = {
+  fromSupabaseToDomain: (
+    supabaseHabitHistory: SupabaseHabitProgress[],
+    goal: Goal,
+  ): HabitProgress[] => {
+    return supabaseHabitHistory.map((item) => {
+      return habitProgressSupabaseDTO.fromSupabaseToDomain(item, goal)
+    })
+  },
+}
diff --git a/infrastructure/supabase/data-transfer-objects/__tests__/HabitDTO.test.ts b/infrastructure/supabase/data-transfer-objects/__tests__/HabitDTO.test.ts
new file mode 100644
index 0000000..c797a85
--- /dev/null
+++ b/infrastructure/supabase/data-transfer-objects/__tests__/HabitDTO.test.ts
@@ -0,0 +1,100 @@
+import type { GoalCreateData } from "@/domain/entities/Goal"
+import { HABIT_MOCK } from "@/tests/mocks/domain/Habit"
+import { SUPABASE_HABIT_MOCK } from "@/tests/mocks/supabase/Habit"
+import { habitSupabaseDTO, habitsSupabaseDTO } from "../HabitDTO"
+
+describe("infrastructure/supabase/data-transfer-objects/HabitDTO", () => {
+  describe("habitSupabaseDTO.fromSupabaseToDomain", () => {
+    for (const example of SUPABASE_HABIT_MOCK.examples) {
+      it(`should return correct Habit entity - ${example.name}`, () => {
+        expect(habitSupabaseDTO.fromSupabaseToDomain(example)).toEqual(
+          HABIT_MOCK.examplesByNames[
+            example.name as keyof typeof HABIT_MOCK.examplesByNames
+          ],
+        )
+      })
+    }
+  })
+
+  describe("habitSupabaseDTO.fromDomainCreateDataToSupabaseInsert", () => {
+    for (const example of HABIT_MOCK.examples) {
+      it(`should return correct SupabaseHabitInsert entity - ${example.name}`, () => {
+        let goalData = {} as GoalCreateData
+        if (example.goal.isBoolean()) {
+          goalData = {
+            frequency: example.goal.frequency,
+            target: { type: "boolean" },
+          }
+        }
+        if (example.goal.isNumeric()) {
+          goalData = {
+            frequency: example.goal.frequency,
+            target: {
+              type: "numeric",
+              value: example.goal.target.value,
+              unit: example.goal.target.unit,
+            },
+          }
+        }
+
+        const supabaseData =
+          SUPABASE_HABIT_MOCK.examplesByNames[
+            example.name as keyof typeof SUPABASE_HABIT_MOCK.examplesByNames
+          ]
+        expect(
+          habitSupabaseDTO.fromDomainCreateDataToSupabaseInsert({
+            userId: example.userId,
+            name: example.name,
+            color: example.color,
+            icon: example.icon,
+            goal: goalData,
+          }),
+        ).toEqual({
+          name: supabaseData.name,
+          color: supabaseData.color,
+          icon: supabaseData.icon,
+          goal_frequency: supabaseData.goal_frequency,
+          ...(supabaseData.goal_target != null &&
+          supabaseData.goal_target_unit != null
+            ? {
+                goal_target: supabaseData.goal_target,
+                goal_target_unit: supabaseData.goal_target_unit,
+              }
+            : {}),
+        })
+      })
+    }
+  })
+
+  describe("habitSupabaseDTO.fromDomainEditDataToSupabaseUpdate", () => {
+    for (const example of HABIT_MOCK.examples) {
+      it(`should return correct SupabaseHabitUpdate entity - ${example.name}`, () => {
+        const supabaseData =
+          SUPABASE_HABIT_MOCK.examplesByNames[
+            example.name as keyof typeof SUPABASE_HABIT_MOCK.examplesByNames
+          ]
+        expect(
+          habitSupabaseDTO.fromDomainEditDataToSupabaseUpdate({
+            name: example.name,
+            color: example.color,
+            icon: example.icon,
+            id: example.id,
+            userId: example.userId,
+          }),
+        ).toEqual({
+          name: supabaseData.name,
+          color: supabaseData.color,
+          icon: supabaseData.icon,
+        })
+      })
+    }
+  })
+
+  describe("habitsSupabaseDTO.fromSupabaseToDomain", () => {
+    it("should return correct Habits entities", () => {
+      expect(
+        habitsSupabaseDTO.fromSupabaseToDomain(SUPABASE_HABIT_MOCK.examples),
+      ).toEqual(HABIT_MOCK.examples)
+    })
+  })
+})
diff --git a/infrastructure/supabase/data-transfer-objects/__tests__/HabitProgressDTO.test.ts b/infrastructure/supabase/data-transfer-objects/__tests__/HabitProgressDTO.test.ts
new file mode 100644
index 0000000..ab8b21f
--- /dev/null
+++ b/infrastructure/supabase/data-transfer-objects/__tests__/HabitProgressDTO.test.ts
@@ -0,0 +1,22 @@
+import type { Habit } from "@/domain/entities/Habit"
+import { HABIT_MOCK } from "@/tests/mocks/domain/Habit"
+import { HABIT_PROGRESS_MOCK } from "@/tests/mocks/domain/HabitProgress"
+import { SUPABASE_HABIT_PROGRESS_MOCK } from "@/tests/mocks/supabase/HabitProgress"
+import { habitProgressSupabaseDTO } from "../HabitProgressDTO"
+
+describe("infrastructure/supabase/data-transfer-objects/HabitProgressDTO", () => {
+  describe("habitProgressSupabaseDTO.fromSupabaseToDomain", () => {
+    for (const example of SUPABASE_HABIT_PROGRESS_MOCK.examples) {
+      it(`should return correct HabitProgress entity - ${example.id}`, () => {
+        const habit = HABIT_MOCK.examplesByIds[example.habit_id] as Habit
+        expect(
+          habitProgressSupabaseDTO.fromSupabaseToDomain(example, habit.goal),
+        ).toEqual(
+          HABIT_PROGRESS_MOCK.exampleByIds[
+            example.id as keyof typeof HABIT_PROGRESS_MOCK.exampleByIds
+          ],
+        )
+      })
+    }
+  })
+})
diff --git a/infrastructure/supabase/repositories/Authentication.ts b/infrastructure/supabase/repositories/Authentication.ts
index 03f1604..75f1206 100644
--- a/infrastructure/supabase/repositories/Authentication.ts
+++ b/infrastructure/supabase/repositories/Authentication.ts
@@ -1,8 +1,8 @@
 import type { Session } from "@supabase/supabase-js"
 
-import type { AuthenticationRepository } from "@/domain/repositories/Authentication"
-import { SupabaseRepository } from "./_SupabaseRepository"
 import { User } from "@/domain/entities/User"
+import type { AuthenticationRepository } from "@/domain/repositories/Authentication"
+import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
 
 export class AuthenticationSupabaseRepository
   extends SupabaseRepository
diff --git a/infrastructure/supabase/repositories/GetHabitProgressHistory.ts b/infrastructure/supabase/repositories/GetHabitProgressHistory.ts
index 9e5c1f0..dd47c0d 100644
--- a/infrastructure/supabase/repositories/GetHabitProgressHistory.ts
+++ b/infrastructure/supabase/repositories/GetHabitProgressHistory.ts
@@ -1,11 +1,6 @@
 import type { GetHabitProgressHistoryRepository } from "@/domain/repositories/GetHabitProgressHistory"
-import { SupabaseRepository } from "./_SupabaseRepository"
-import { HabitProgress } from "@/domain/entities/HabitProgress"
-import type { GoalProgress } from "@/domain/entities/Goal"
-import {
-  GoalBooleanProgress,
-  GoalNumericProgress,
-} from "@/domain/entities/Goal"
+import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
+import { habitProgressHistorySupabaseDTO } from "../data-transfer-objects/HabitProgressDTO"
 
 export class GetHabitProgressHistorySupabaseRepository
   extends SupabaseRepository
@@ -15,37 +10,15 @@ export class GetHabitProgressHistorySupabaseRepository
     options,
   ) => {
     const { habit } = options
-    const { data, error } = await this.supabaseClient
+    const { data } = await this.supabaseClient
       .from("habits_progresses")
       .select("*")
       .eq("habit_id", habit.id)
-    if (error != null) {
-      throw new Error(error.message)
-    }
-    const habitProgressHistory = data.map((item) => {
-      let goalProgress: GoalProgress | null = null
-      if (habit.goal.isNumeric()) {
-        goalProgress = new GoalNumericProgress({
-          goal: habit.goal,
-          progress: item.goal_progress,
-        })
-      } else if (habit.goal.isBoolean()) {
-        goalProgress = new GoalBooleanProgress({
-          goal: habit.goal,
-          progress: item.goal_progress === 1,
-        })
-      }
-      if (goalProgress == null) {
-        throw new Error("Goal progress is null.")
-      }
-      const habitProgress = new HabitProgress({
-        id: item.id.toString(),
-        habitId: item.habit_id.toString(),
-        goalProgress,
-        date: new Date(item.date),
-      })
-      return habitProgress
-    })
-    return habitProgressHistory
+      .throwOnError()
+    const habitProgressHistory = data as NonNullable<typeof data>
+    return habitProgressHistorySupabaseDTO.fromSupabaseToDomain(
+      habitProgressHistory,
+      habit.goal,
+    )
   }
 }
diff --git a/infrastructure/supabase/repositories/GetHabitsByUserId.ts b/infrastructure/supabase/repositories/GetHabitsByUserId.ts
index 3d9c022..6026fa6 100644
--- a/infrastructure/supabase/repositories/GetHabitsByUserId.ts
+++ b/infrastructure/supabase/repositories/GetHabitsByUserId.ts
@@ -1,8 +1,6 @@
 import type { GetHabitsByUserIdRepository } from "@/domain/repositories/GetHabitsByUserId"
-import { SupabaseRepository } from "./_SupabaseRepository"
-import { Habit } from "@/domain/entities/Habit"
-import type { Goal } from "@/domain/entities/Goal"
-import { GoalBoolean, GoalNumeric } from "@/domain/entities/Goal"
+import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
+import { habitsSupabaseDTO } from "../data-transfer-objects/HabitDTO"
 
 export class GetHabitsByUserIdSupabaseRepository
   extends SupabaseRepository
@@ -10,39 +8,12 @@ export class GetHabitsByUserIdSupabaseRepository
 {
   public execute: GetHabitsByUserIdRepository["execute"] = async (options) => {
     const { userId } = options
-    const { data, error } = await this.supabaseClient
+    const { data } = await this.supabaseClient
       .from("habits")
       .select("*")
       .eq("user_id", userId)
-    if (error != null) {
-      throw new Error(error.message)
-    }
-    return data.map((item) => {
-      let goal: Goal
-      if (item.goal_target != null && item.goal_target_unit != null) {
-        goal = new GoalNumeric({
-          frequency: item.goal_frequency,
-          target: {
-            value: item.goal_target,
-            unit: item.goal_target_unit,
-          },
-        })
-      } else {
-        goal = new GoalBoolean({
-          frequency: item.goal_frequency,
-        })
-      }
-      const habit = new Habit({
-        id: item.id.toString(),
-        name: item.name,
-        color: item.color,
-        icon: item.icon,
-        userId: item.user_id.toString(),
-        startDate: new Date(item.start_date),
-        endDate: item.end_date != null ? new Date(item.end_date) : undefined,
-        goal,
-      })
-      return habit
-    })
+      .throwOnError()
+    const habits = data as NonNullable<typeof data>
+    return habitsSupabaseDTO.fromSupabaseToDomain(habits)
   }
 }
diff --git a/infrastructure/supabase/repositories/HabitCreate.ts b/infrastructure/supabase/repositories/HabitCreate.ts
index 04e5ab3..58f17a8 100644
--- a/infrastructure/supabase/repositories/HabitCreate.ts
+++ b/infrastructure/supabase/repositories/HabitCreate.ts
@@ -1,7 +1,6 @@
-import { Habit } from "@/domain/entities/Habit"
 import type { HabitCreateRepository } from "@/domain/repositories/HabitCreate"
-import { SupabaseRepository } from "./_SupabaseRepository"
-import { Goal } from "@/domain/entities/Goal"
+import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
+import { habitSupabaseDTO } from "../data-transfer-objects/HabitDTO"
 
 export class HabitCreateSupabaseRepository
   extends SupabaseRepository
@@ -9,34 +8,15 @@ export class HabitCreateSupabaseRepository
 {
   public execute: HabitCreateRepository["execute"] = async (options) => {
     const { habitCreateData } = options
-    const { data, error } = await this.supabaseClient
+    const { data } = await this.supabaseClient
       .from("habits")
-      .insert({
-        name: habitCreateData.name,
-        color: habitCreateData.color,
-        icon: habitCreateData.icon,
-        goal_frequency: habitCreateData.goal.frequency,
-        ...(habitCreateData.goal.target.type === "numeric"
-          ? {
-              goal_target: habitCreateData.goal.target.value,
-              goal_target_unit: habitCreateData.goal.target.unit,
-            }
-          : {}),
-      })
+      .insert(
+        habitSupabaseDTO.fromDomainCreateDataToSupabaseInsert(habitCreateData),
+      )
       .select("*")
-    const insertedHabit = data?.[0]
-    if (error != null || insertedHabit == null) {
-      throw new Error(error?.message ?? "Failed to create habit.")
-    }
-    const habit = new Habit({
-      id: insertedHabit.id.toString(),
-      userId: insertedHabit.user_id.toString(),
-      name: insertedHabit.name,
-      icon: insertedHabit.icon,
-      goal: Goal.create(habitCreateData.goal),
-      color: insertedHabit.color,
-      startDate: new Date(insertedHabit.start_date),
-    })
-    return habit
+      .single()
+      .throwOnError()
+    const insertedHabit = data as NonNullable<typeof data>
+    return habitSupabaseDTO.fromSupabaseToDomain(insertedHabit)
   }
 }
diff --git a/infrastructure/supabase/repositories/HabitEdit.ts b/infrastructure/supabase/repositories/HabitEdit.ts
index 4938707..bd417e2 100644
--- a/infrastructure/supabase/repositories/HabitEdit.ts
+++ b/infrastructure/supabase/repositories/HabitEdit.ts
@@ -1,7 +1,6 @@
-import { Habit } from "@/domain/entities/Habit"
 import type { HabitEditRepository } from "@/domain/repositories/HabitEdit"
-import { SupabaseRepository } from "./_SupabaseRepository"
-import { Goal } from "@/domain/entities/Goal"
+import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
+import { habitSupabaseDTO } from "../data-transfer-objects/HabitDTO"
 
 export class HabitEditSupabaseRepository
   extends SupabaseRepository
@@ -9,46 +8,16 @@ export class HabitEditSupabaseRepository
 {
   public execute: HabitEditRepository["execute"] = async (options) => {
     const { habitEditData } = options
-    const { data, error } = await this.supabaseClient
+    const { data } = await this.supabaseClient
       .from("habits")
-      .update({
-        name: habitEditData.name,
-        color: habitEditData.color,
-        icon: habitEditData.icon,
-        end_date: habitEditData?.endDate?.toISOString(),
-      })
+      .update(
+        habitSupabaseDTO.fromDomainEditDataToSupabaseUpdate(habitEditData),
+      )
       .eq("id", habitEditData.id)
       .select("*")
-    const updatedHabit = data?.[0]
-    if (error != null || updatedHabit == null) {
-      throw new Error(error?.message ?? "Failed to edit habit.")
-    }
-    const habit = new Habit({
-      id: updatedHabit.id.toString(),
-      userId: updatedHabit.user_id.toString(),
-      name: updatedHabit.name,
-      icon: updatedHabit.icon,
-      goal: Goal.create({
-        frequency: updatedHabit.goal_frequency,
-        target:
-          updatedHabit.goal_target != null &&
-          updatedHabit.goal_target_unit != null
-            ? {
-                type: "numeric",
-                value: updatedHabit.goal_target,
-                unit: updatedHabit.goal_target_unit,
-              }
-            : {
-                type: "boolean",
-              },
-      }),
-      color: updatedHabit.color,
-      startDate: new Date(updatedHabit.start_date),
-      endDate:
-        updatedHabit.end_date != null
-          ? new Date(updatedHabit.end_date)
-          : undefined,
-    })
-    return habit
+      .single()
+      .throwOnError()
+    const updatedHabit = data as NonNullable<typeof data>
+    return habitSupabaseDTO.fromSupabaseToDomain(updatedHabit)
   }
 }
diff --git a/infrastructure/supabase/repositories/HabitProgressCreate.ts b/infrastructure/supabase/repositories/HabitProgressCreate.ts
index 90e4979..8a97c48 100644
--- a/infrastructure/supabase/repositories/HabitProgressCreate.ts
+++ b/infrastructure/supabase/repositories/HabitProgressCreate.ts
@@ -1,6 +1,6 @@
 import type { HabitProgressCreateRepository } from "@/domain/repositories/HabitProgressCreate"
-import { SupabaseRepository } from "./_SupabaseRepository"
-import { HabitProgress } from "@/domain/entities/HabitProgress"
+import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
+import { habitProgressSupabaseDTO } from "../data-transfer-objects/HabitProgressDTO"
 
 export class HabitProgressCreateSupabaseRepository
   extends SupabaseRepository
@@ -10,29 +10,20 @@ export class HabitProgressCreateSupabaseRepository
     options,
   ) => {
     const { habitProgressData } = options
-    const { goalProgress, date, habitId } = habitProgressData
-    let goalProgressValue = goalProgress.isCompleted() ? 1 : 0
-    if (goalProgress.isNumeric()) {
-      goalProgressValue = goalProgress.progress
-    }
-    const { data, error } = await this.supabaseClient
+    const { data } = await this.supabaseClient
       .from("habits_progresses")
-      .insert({
-        habit_id: Number(habitId),
-        date: date.toISOString(),
-        goal_progress: goalProgressValue,
-      })
+      .insert(
+        habitProgressSupabaseDTO.fromDomainDataToSupabaseInsert(
+          habitProgressData,
+        ),
+      )
       .select("*")
-    const insertedProgress = data?.[0]
-    if (error != null || insertedProgress == null) {
-      throw new Error(error?.message ?? "Failed to create habit progress.")
-    }
-    const habitProgress = new HabitProgress({
-      id: insertedProgress.id.toString(),
-      habitId: insertedProgress.habit_id.toString(),
-      date: new Date(insertedProgress.date),
-      goalProgress,
-    })
-    return habitProgress
+      .single()
+      .throwOnError()
+    const insertedProgress = data as NonNullable<typeof data>
+    return habitProgressSupabaseDTO.fromSupabaseToDomain(
+      insertedProgress,
+      habitProgressData.goalProgress.goal,
+    )
   }
 }
diff --git a/infrastructure/supabase/repositories/HabitProgressUpdate.ts b/infrastructure/supabase/repositories/HabitProgressUpdate.ts
index b7b437b..da04eb3 100644
--- a/infrastructure/supabase/repositories/HabitProgressUpdate.ts
+++ b/infrastructure/supabase/repositories/HabitProgressUpdate.ts
@@ -1,6 +1,6 @@
 import type { HabitProgressUpdateRepository } from "@/domain/repositories/HabitProgressUpdate"
-import { SupabaseRepository } from "./_SupabaseRepository"
-import { HabitProgress } from "@/domain/entities/HabitProgress"
+import { SupabaseRepository } from "@/infrastructure/supabase/repositories/_SupabaseRepository"
+import { habitProgressSupabaseDTO } from "../data-transfer-objects/HabitProgressDTO"
 
 export class HabitProgressUpdateSupabaseRepository
   extends SupabaseRepository
@@ -10,29 +10,21 @@ export class HabitProgressUpdateSupabaseRepository
     options,
   ) => {
     const { habitProgressData } = options
-    const { id, goalProgress, date } = habitProgressData
-    let goalProgressValue = goalProgress.isCompleted() ? 1 : 0
-    if (goalProgress.isNumeric()) {
-      goalProgressValue = goalProgress.progress
-    }
-    const { data, error } = await this.supabaseClient
+    const { data } = await this.supabaseClient
       .from("habits_progresses")
-      .update({
-        date: date.toISOString(),
-        goal_progress: goalProgressValue,
-      })
-      .eq("id", id)
+      .update(
+        habitProgressSupabaseDTO.fromDomainDataToSupabaseUpdate(
+          habitProgressData,
+        ),
+      )
+      .eq("id", habitProgressData.id)
       .select("*")
-    const insertedProgress = data?.[0]
-    if (error != null || insertedProgress == null) {
-      throw new Error(error?.message ?? "Failed to update habit progress.")
-    }
-    const habitProgress = new HabitProgress({
-      id: insertedProgress.id.toString(),
-      habitId: insertedProgress.habit_id.toString(),
-      date: new Date(insertedProgress.date),
-      goalProgress,
-    })
-    return habitProgress
+      .single()
+      .throwOnError()
+    const insertedProgress = data as NonNullable<typeof data>
+    return habitProgressSupabaseDTO.fromSupabaseToDomain(
+      insertedProgress,
+      habitProgressData.goalProgress.goal,
+    )
   }
 }
diff --git a/infrastructure/supabase/supabase.ts b/infrastructure/supabase/supabase.ts
index 8a1dc59..c3fb1f8 100644
--- a/infrastructure/supabase/supabase.ts
+++ b/infrastructure/supabase/supabase.ts
@@ -9,9 +9,19 @@ import AsyncStorage from "@react-native-async-storage/async-storage"
 import type { Database } from "./supabase-types"
 
 export type SupabaseUser = SupabaseUserType
+
 export type SupabaseHabit = Database["public"]["Tables"]["habits"]["Row"]
+export type SupabaseHabitInsert =
+  Database["public"]["Tables"]["habits"]["Insert"]
+export type SupabaseHabitUpdate =
+  Database["public"]["Tables"]["habits"]["Update"]
+
 export type SupabaseHabitProgress =
   Database["public"]["Tables"]["habits_progresses"]["Row"]
+export type SupabaseHabitProgressInsert =
+  Database["public"]["Tables"]["habits_progresses"]["Insert"]
+export type SupabaseHabitProgressUpdate =
+  Database["public"]["Tables"]["habits_progresses"]["Update"]
 
 const SUPABASE_URL =
   process.env["EXPO_PUBLIC_SUPABASE_URL"] ??
diff --git a/jest.config.json b/jest.config.json
index 5909eb4..e75d002 100644
--- a/jest.config.json
+++ b/jest.config.json
@@ -10,7 +10,13 @@
   "coverageReporters": ["text", "text-summary", "cobertura"],
   "collectCoverageFrom": [
     "<rootDir>/**/*.{ts,tsx}",
+    "!<rootDir>/tests/**/*",
+    "!<rootDir>/domain/repositories/**/*",
+    "!<rootDir>/infrastructure/instances.ts",
+    "!<rootDir>/infrastructure/supabase/supabase-types.ts",
+    "!<rootDir>/infrastructure/supabase/supabase.ts",
     "!<rootDir>/presentation/react-native/ui/ExternalLink.tsx",
+    "!<rootDir>/presentation/react/contexts/**/*",
     "!<rootDir>/.expo",
     "!<rootDir>/app/+html.tsx",
     "!<rootDir>/app/**/_layout.tsx",
diff --git a/presentation/presenters/_Presenter.ts b/presentation/presenters/_Presenter.ts
index 1f53ccb..e500275 100644
--- a/presentation/presenters/_Presenter.ts
+++ b/presentation/presenters/_Presenter.ts
@@ -41,10 +41,7 @@ export abstract class Presenter<State> {
 
   public unsubscribe(listener: Listener<State>): void {
     const listenerIndex = this._listeners.indexOf(listener)
-    const listenerFound = listenerIndex !== -1
-    if (listenerFound) {
-      this._listeners.splice(listenerIndex, 1)
-    }
+    this._listeners.splice(listenerIndex, 1)
   }
 
   private notifyListeners(): void {
diff --git a/presentation/react-native/components/HabitForm/IconSelectorModal.tsx b/presentation/react-native/components/HabitForm/IconSelectorModal.tsx
index 20389c6..d130c8b 100644
--- a/presentation/react-native/components/HabitForm/IconSelectorModal.tsx
+++ b/presentation/react-native/components/HabitForm/IconSelectorModal.tsx
@@ -1,9 +1,9 @@
+import type { IconName } from "@fortawesome/fontawesome-svg-core"
 import { fas } from "@fortawesome/free-solid-svg-icons"
+import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"
 import { memo, useCallback, useEffect, useState, useTransition } from "react"
 import { Modal, ScrollView, View } from "react-native"
 import { Button, List, Text, TextInput } from "react-native-paper"
-import type { IconName } from "@fortawesome/fontawesome-svg-core"
-import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"
 
 import { IconsList } from "./IconsList"
 
diff --git a/presentation/react-native/components/HabitsMainPage/HabitCard.tsx b/presentation/react-native/components/HabitsMainPage/HabitCard.tsx
index 4f032d3..c7b40cd 100644
--- a/presentation/react-native/components/HabitsMainPage/HabitCard.tsx
+++ b/presentation/react-native/components/HabitsMainPage/HabitCard.tsx
@@ -1,16 +1,16 @@
+import type { IconName } from "@fortawesome/free-solid-svg-icons"
 import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"
 import { useRouter } from "expo-router"
+import type LottieView from "lottie-react-native"
 import { useState } from "react"
 import { View } from "react-native"
 import { Checkbox, List, Text } from "react-native-paper"
-import type LottieView from "lottie-react-native"
-import type { IconName } from "@fortawesome/free-solid-svg-icons"
 
 import type { GoalBoolean } from "@/domain/entities/Goal"
 import { GoalBooleanProgress } from "@/domain/entities/Goal"
 import type { HabitHistory } from "@/domain/entities/HabitHistory"
-import { getColorRGBAFromHex } from "@/utils/colors"
 import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
+import { getColorRGBAFromHex } from "@/utils/colors"
 
 export interface HabitCardProps {
   habitHistory: HabitHistory
diff --git a/presentation/react-native/components/HabitsMainPage/HabitsList.tsx b/presentation/react-native/components/HabitsMainPage/HabitsList.tsx
index 0195432..beacc11 100644
--- a/presentation/react-native/components/HabitsMainPage/HabitsList.tsx
+++ b/presentation/react-native/components/HabitsMainPage/HabitsList.tsx
@@ -5,8 +5,8 @@ import { Divider, List } from "react-native-paper"
 
 import type { GoalFrequency } from "@/domain/entities/Goal"
 import type { HabitsTracker } from "@/domain/entities/HabitsTracker"
-import confettiJSON from "../../../assets/confetti.json"
 import { capitalize } from "@/utils/strings"
+import confettiJSON from "../../../assets/confetti.json"
 import { HabitCard } from "./HabitCard"
 
 export interface HabitsListProps {
diff --git a/presentation/react/contexts/Authentication.tsx b/presentation/react/contexts/Authentication.tsx
index 85c507c..6809431 100644
--- a/presentation/react/contexts/Authentication.tsx
+++ b/presentation/react/contexts/Authentication.tsx
@@ -1,11 +1,11 @@
 import { createContext, useContext, useEffect } from "react"
 
-import { usePresenterState } from "@/presentation/react/hooks/usePresenterState"
+import { authenticationPresenter } from "@/infrastructure/instances"
 import type {
   AuthenticationPresenter,
   AuthenticationPresenterState,
 } from "@/presentation/presenters/Authentication"
-import { authenticationPresenter } from "@/infrastructure/instances"
+import { usePresenterState } from "@/presentation/react/hooks/usePresenterState"
 
 export interface AuthenticationContextValue
   extends AuthenticationPresenterState {
diff --git a/presentation/react/contexts/HabitsTracker.tsx b/presentation/react/contexts/HabitsTracker.tsx
index a4b1948..bded388 100644
--- a/presentation/react/contexts/HabitsTracker.tsx
+++ b/presentation/react/contexts/HabitsTracker.tsx
@@ -1,11 +1,11 @@
 import { createContext, useContext, useEffect } from "react"
 
+import { habitsTrackerPresenter } from "@/infrastructure/instances"
 import type {
   HabitsTrackerPresenter,
   HabitsTrackerPresenterState,
 } from "@/presentation/presenters/HabitsTracker"
 import { usePresenterState } from "@/presentation/react/hooks/usePresenterState"
-import { habitsTrackerPresenter } from "@/infrastructure/instances"
 import { useAuthentication } from "./Authentication"
 
 export interface HabitsTrackerContextValue extends HabitsTrackerPresenterState {
diff --git a/presentation/react/hooks/__tests__/usePresenterState.test.ts b/presentation/react/hooks/__tests__/usePresenterState.test.ts
index 4c71adf..5c7d6e0 100644
--- a/presentation/react/hooks/__tests__/usePresenterState.test.ts
+++ b/presentation/react/hooks/__tests__/usePresenterState.test.ts
@@ -1,7 +1,7 @@
 import { act, renderHook } from "@testing-library/react-native"
 
-import { usePresenterState } from "@/presentation/react/hooks/usePresenterState"
 import { Presenter } from "@/presentation/presenters/_Presenter"
+import { usePresenterState } from "@/presentation/react/hooks/usePresenterState"
 
 interface MockCountPresenterState {
   count: number
diff --git a/tests/mocks/domain/Habit.ts b/tests/mocks/domain/Habit.ts
new file mode 100644
index 0000000..5c2ddfb
--- /dev/null
+++ b/tests/mocks/domain/Habit.ts
@@ -0,0 +1,115 @@
+import { GoalBoolean, GoalNumeric } from "@/domain/entities/Goal"
+import type { HabitData } from "@/domain/entities/Habit"
+import { Habit } from "@/domain/entities/Habit"
+import { USER_MOCK } from "./User"
+import { ONE_DAY_MILLISECONDS } from "@/utils/dates"
+
+interface HabitMockCreateOptions extends Omit<HabitData, "startDate"> {
+  startDate?: Date
+}
+const habitMockCreate = (options: HabitMockCreateOptions): Habit => {
+  const {
+    id,
+    userId,
+    name,
+    color,
+    icon,
+    goal,
+    startDate = new Date(),
+    endDate,
+  } = options
+
+  return new Habit({
+    id,
+    userId,
+    name,
+    color,
+    icon,
+    goal,
+    startDate,
+    endDate,
+  })
+}
+
+const examplesByNames = {
+  "Wake up at 07h00": habitMockCreate({
+    id: "1",
+    userId: USER_MOCK.example.id,
+    name: "Wake up at 07h00",
+    color: "#006CFF",
+    icon: "bed",
+    goal: new GoalBoolean({
+      frequency: "daily",
+    }),
+  }),
+  "Learn English": habitMockCreate({
+    id: "2",
+    userId: USER_MOCK.example.id,
+    name: "Learn English",
+    color: "#EB4034",
+    icon: "language",
+    goal: new GoalNumeric({
+      frequency: "daily",
+      target: {
+        value: 30,
+        unit: "minutes",
+      },
+    }),
+  }),
+  Walk: habitMockCreate({
+    id: "3",
+    userId: USER_MOCK.example.id,
+    name: "Walk",
+    color: "#228B22",
+    icon: "person-walking",
+    goal: new GoalNumeric({
+      frequency: "daily",
+      target: {
+        value: 5000,
+        unit: "steps",
+      },
+    }),
+  }),
+  "Clean the house": habitMockCreate({
+    id: "4",
+    userId: USER_MOCK.example.id,
+    name: "Clean the house",
+    color: "#808080",
+    icon: "broom",
+    goal: new GoalBoolean({
+      frequency: "weekly",
+    }),
+  }),
+  "Solve Programming Challenges": habitMockCreate({
+    id: "5",
+    userId: USER_MOCK.example.id,
+    name: "Solve Programming Challenges",
+    color: "#DE3163",
+    icon: "code",
+    goal: new GoalNumeric({
+      frequency: "monthly",
+      target: {
+        value: 5,
+        unit: "challenges",
+      },
+    }),
+    endDate: new Date(Date.now() + ONE_DAY_MILLISECONDS),
+  }),
+} as const
+
+export const examplesByIds = {
+  [examplesByNames["Wake up at 07h00"].id]: examplesByNames["Wake up at 07h00"],
+  [examplesByNames["Learn English"].id]: examplesByNames["Learn English"],
+  [examplesByNames.Walk.id]: examplesByNames.Walk,
+  [examplesByNames["Clean the house"].id]: examplesByNames["Clean the house"],
+  [examplesByNames["Solve Programming Challenges"].id]:
+    examplesByNames["Solve Programming Challenges"],
+} as const
+
+export const HABIT_MOCK = {
+  create: habitMockCreate,
+  example: examplesByNames["Wake up at 07h00"],
+  examplesByNames,
+  examplesByIds,
+  examples: Object.values(examplesByNames),
+}
diff --git a/tests/mocks/domain/HabitProgress.ts b/tests/mocks/domain/HabitProgress.ts
new file mode 100644
index 0000000..7496ba1
--- /dev/null
+++ b/tests/mocks/domain/HabitProgress.ts
@@ -0,0 +1,51 @@
+import type { GoalBoolean, GoalNumeric } from "@/domain/entities/Goal"
+import {
+  GoalBooleanProgress,
+  GoalNumericProgress,
+} from "@/domain/entities/Goal"
+import type { HabitProgressData } from "@/domain/entities/HabitProgress"
+import { HabitProgress } from "@/domain/entities/HabitProgress"
+import { HABIT_MOCK } from "./Habit"
+
+interface HabitProgressMockCreateOptions
+  extends Omit<HabitProgressData, "date"> {
+  date?: Date
+}
+
+const habitProgressMockCreate = (
+  options: HabitProgressMockCreateOptions,
+): HabitProgress => {
+  const { id, habitId, goalProgress, date = new Date() } = options
+
+  return new HabitProgress({
+    date,
+    goalProgress,
+    habitId,
+    id,
+  })
+}
+
+const exampleByIds = {
+  1: habitProgressMockCreate({
+    id: "1",
+    habitId: HABIT_MOCK.examplesByNames["Clean the house"].id,
+    goalProgress: new GoalBooleanProgress({
+      goal: HABIT_MOCK.examplesByNames["Clean the house"].goal as GoalBoolean,
+      progress: true,
+    }),
+  }),
+  2: habitProgressMockCreate({
+    id: "2",
+    habitId: HABIT_MOCK.examplesByNames.Walk.id,
+    goalProgress: new GoalNumericProgress({
+      goal: HABIT_MOCK.examplesByNames.Walk.goal as GoalNumeric,
+      progress: 4_733,
+    }),
+  }),
+} as const
+
+export const HABIT_PROGRESS_MOCK = {
+  create: habitProgressMockCreate,
+  exampleByIds,
+  examples: Object.values(exampleByIds),
+}
diff --git a/tests/mocks/domain/User.ts b/tests/mocks/domain/User.ts
new file mode 100644
index 0000000..40aa91c
--- /dev/null
+++ b/tests/mocks/domain/User.ts
@@ -0,0 +1,30 @@
+import type { UserData } from "@/domain/entities/User"
+import { User } from "@/domain/entities/User"
+
+const USER_MOCK_ID = "ab054ee9-fbb4-473e-942b-bbf4415f4bef"
+const USER_MOCK_EMAIL = "test@test.com"
+const USER_MOCK_DISPLAY_NAME = "Test"
+
+interface UserMockCreateOptions {
+  id?: UserData["id"]
+  email?: UserData["email"]
+  displayName?: UserData["displayName"]
+}
+const userMockCreate = (options: UserMockCreateOptions = {}): User => {
+  const {
+    id = USER_MOCK_ID,
+    email = USER_MOCK_EMAIL,
+    displayName = USER_MOCK_DISPLAY_NAME,
+  } = options
+
+  return new User({
+    id,
+    email,
+    displayName,
+  })
+}
+
+export const USER_MOCK = {
+  create: userMockCreate,
+  example: userMockCreate(),
+}
diff --git a/tests/mocks/supabase/Habit.ts b/tests/mocks/supabase/Habit.ts
new file mode 100644
index 0000000..e399e7f
--- /dev/null
+++ b/tests/mocks/supabase/Habit.ts
@@ -0,0 +1,79 @@
+import type { SupabaseHabit } from "@/infrastructure/supabase/supabase"
+import { HABIT_MOCK } from "../domain/Habit"
+import { SUPABASE_USER_MOCK } from "./User"
+
+interface SupabaseHabitMockCreateOptions {
+  id: SupabaseHabit["id"]
+  userId: SupabaseHabit["user_id"]
+  name: SupabaseHabit["name"]
+  color: SupabaseHabit["color"]
+  icon: SupabaseHabit["icon"]
+  startDate?: Date
+  endDate: Date | null
+  goalFrequency: SupabaseHabit["goal_frequency"]
+  goalTarget: SupabaseHabit["goal_target"] | null
+  goalTargetUnit: SupabaseHabit["goal_target_unit"] | null
+}
+const supabaseHabitMockCreate = (
+  options: SupabaseHabitMockCreateOptions,
+): SupabaseHabit => {
+  const {
+    id,
+    userId,
+    name,
+    color,
+    icon,
+    startDate = new Date(),
+    endDate,
+    goalFrequency,
+    goalTarget,
+    goalTargetUnit,
+  } = options
+
+  return {
+    id,
+    user_id: userId,
+    name,
+    color,
+    icon,
+    start_date: startDate.toISOString(),
+    end_date: endDate?.toISOString() ?? null,
+    goal_frequency: goalFrequency,
+    goal_target: goalTarget,
+    goal_target_unit: goalTargetUnit,
+  }
+}
+
+const examplesByNames = Object.fromEntries(
+  Object.entries(HABIT_MOCK.examplesByNames).map(([name, habit]) => {
+    const goalTarget = habit.goal.isNumeric() ? habit.goal.target.value : null
+    const goalTargetUnit = habit.goal.isNumeric()
+      ? habit.goal.target.unit
+      : null
+    return [
+      name,
+      supabaseHabitMockCreate({
+        id: Number.parseInt(habit.id, 10),
+        userId: SUPABASE_USER_MOCK.example.id,
+        name: habit.name,
+        color: habit.color,
+        icon: habit.icon,
+        startDate: habit.startDate,
+        endDate: habit.endDate ?? null,
+        goalFrequency: habit.goal.frequency,
+        goalTarget,
+        goalTargetUnit,
+      }),
+    ]
+  }),
+) as {
+  [key in keyof (typeof HABIT_MOCK)["examplesByNames"]]: SupabaseHabit
+}
+
+export const SUPABASE_HABIT_MOCK = {
+  create: supabaseHabitMockCreate,
+  example:
+    examplesByNames[HABIT_MOCK.example.name as keyof typeof examplesByNames],
+  examples: Object.values(examplesByNames),
+  examplesByNames,
+}
diff --git a/tests/mocks/supabase/HabitProgress.ts b/tests/mocks/supabase/HabitProgress.ts
new file mode 100644
index 0000000..0fdbd65
--- /dev/null
+++ b/tests/mocks/supabase/HabitProgress.ts
@@ -0,0 +1,49 @@
+import type { SupabaseHabitProgress } from "@/infrastructure/supabase/supabase"
+import { HABIT_PROGRESS_MOCK } from "../domain/HabitProgress"
+
+interface SupabaseHabitProgressMockCreateOptions {
+  id: SupabaseHabitProgress["id"]
+  habitId: SupabaseHabitProgress["habit_id"]
+  date?: Date
+  goalProgress: SupabaseHabitProgress["goal_progress"]
+}
+const supabaseHabitProgressMockCreate = (
+  options: SupabaseHabitProgressMockCreateOptions,
+): SupabaseHabitProgress => {
+  const { id, habitId, date = new Date(), goalProgress } = options
+
+  return {
+    id,
+    habit_id: habitId,
+    date: date.toISOString(),
+    goal_progress: goalProgress,
+  }
+}
+
+const exampleByIds = Object.fromEntries(
+  Object.entries(HABIT_PROGRESS_MOCK.exampleByIds).map(
+    ([id, habitProgress]) => {
+      return [
+        id,
+        supabaseHabitProgressMockCreate({
+          id: Number.parseInt(habitProgress.id, 10),
+          habitId: Number.parseInt(habitProgress.habitId, 10),
+          date: new Date(habitProgress.date),
+          goalProgress: habitProgress.goalProgress.isNumeric()
+            ? habitProgress.goalProgress.progress
+            : habitProgress.goalProgress.isCompleted()
+              ? 1
+              : 0,
+        }),
+      ]
+    },
+  ),
+) as {
+  [key in keyof (typeof HABIT_PROGRESS_MOCK)["exampleByIds"]]: SupabaseHabitProgress
+}
+
+export const SUPABASE_HABIT_PROGRESS_MOCK = {
+  create: supabaseHabitProgressMockCreate,
+  exampleByIds,
+  examples: Object.values(exampleByIds),
+}
diff --git a/tests/mocks/supabase/User.ts b/tests/mocks/supabase/User.ts
new file mode 100644
index 0000000..56257f9
--- /dev/null
+++ b/tests/mocks/supabase/User.ts
@@ -0,0 +1,63 @@
+import type { SupabaseUser } from "@/infrastructure/supabase/supabase"
+import { USER_MOCK } from "../domain/User"
+
+interface SupabaseUserMockCreateOptions {
+  id?: SupabaseUser["id"]
+  email?: SupabaseUser["email"]
+  displayName?: SupabaseUser["user_metadata"]["display_name"]
+  date?: Date
+}
+const supabaseUserMockCreate = (
+  options: SupabaseUserMockCreateOptions = {},
+): SupabaseUser => {
+  const {
+    id = USER_MOCK.example.id,
+    email = USER_MOCK.example.email,
+    displayName = USER_MOCK.example.displayName,
+    date = new Date(),
+  } = options
+
+  return {
+    id,
+    app_metadata: { provider: "email", providers: ["email"] },
+    user_metadata: { display_name: displayName },
+    aud: "authenticated",
+    email,
+    confirmation_sent_at: undefined,
+    recovery_sent_at: undefined,
+    email_change_sent_at: undefined,
+    new_email: "",
+    new_phone: "",
+    invited_at: undefined,
+    action_link: "",
+    created_at: date.toISOString(),
+    confirmed_at: undefined,
+    email_confirmed_at: date.toISOString(),
+    phone_confirmed_at: undefined,
+    last_sign_in_at: undefined,
+    role: "authenticated",
+    updated_at: date.toISOString(),
+    identities: [
+      {
+        id,
+        user_id: id,
+        identity_data: {
+          sub: id,
+          email,
+        },
+        provider: "email",
+        identity_id: id,
+        last_sign_in_at: date.toISOString(),
+        created_at: date.toISOString(),
+        updated_at: date.toISOString(),
+      },
+    ],
+    is_anonymous: false,
+    factors: [],
+  }
+}
+
+export const SUPABASE_USER_MOCK = {
+  create: supabaseUserMockCreate,
+  example: supabaseUserMockCreate(),
+}
-- 
GitLab