Belajar Redux - Testing Store & Components
Episode 18 of 23

Belajar Redux - Testing Store & Components

Episode ini membahas strategi testing Redux: menguji reducer secara murni, menguji async thunk dengan mock fetch, merender komponen dengan test store memakai testing-library, serta mengintegrasikan MSW untuk mem-mock API secara realistis tanpa server nyata.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Redux dirancang agar mudah diuji: reducer adalah fungsi murni, dan seluruh logika async tinggal di thunk. Episode 18 mengubah keunggulan itu menjadi praktik — kalian akan belajar menulis test untuk slice, thunk, dan komponen yang memakai Redux, serta mem-mock API dengan MSW sehingga test berjalan cepat dan deterministik.

Strategi pengujian berlapis: unit test untuk reducer yang murni, integration test untuk thunk dengan fetch dimock, lalu component test dengan renderer + store uji. Lapisan ini bersama-sama menjaga agar setiap perubahan kode tidak diam-diam memecahkan perilaku state.

Testing Reducer Secara Murni

Menyiapkan Test Store

Sebelum menulis test, siapkan tooling dan store uji yang dibangun dari slice yang sama dengan aplikasi:

Install tooling testing
npm install -D vitest @testing-library/react @testing-library/jest-dom msw
JSsrc/test/store.ts
import { configureStore } from "@reduxjs/toolkit"
import { authSlice } from "../features/auth/authSlice"
import { postsSlice } from "../features/posts/postsSlice"
 
export const createTestStore = (preloaded = {}) =>
  configureStore({
    reducer: {
      auth: authSlice.reducer,
      posts: postsSlice.reducer,
    },
    preloadedState: preloaded,
  })

createTestStore adalah factory test store dengan state awal yang bisa disuntikkan. Setiap test membuat store baru sehingga tidak ada state bocor antar test.

Test Reducer sebagai Fungsi Murni

Reducer hanyalah fungsi: beri state dan action, periksa hasilnya. postsReducer(initialState, postAdded(...)) adalah panggilan fungsi murni biasa:

JSsrc/features/posts/postsSlice.test.ts
import { expect, it } from "vitest"
import postsReducer, { postAdded } from "./postsSlice"
 
it("menambahkan post baru ke state", () => {
  const initialState = { ids: [], entities: {} }
  const next = postsReducer(
    initialState,
    postAdded({ id: 1, title: "Halo" }),
  )
  expect(next.entities[1]?.title).toBe("Halo")
  expect(next.ids).toContain(1)
})

Testing Async Thunk dengan Mock Fetch

Mem-mock fetch

Thunk diuji dengan mem-mock fetch global dan store uji. Dispatch thunk, lalu tunggu hingga selesai:

JSsrc/features/posts/fetchPosts.test.ts
import { expect, it, vi } from "vitest"
import { createTestStore } from "../../test/store"
import { fetchPosts } from "./postsSlice"
 
it("menyimpan posts saat berhasil", async () => {
  vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
    ok: true,
    json: async () => [{ id: 1, title: "Dari API" }],
  }))
 
  const store = createTestStore()
  await store.dispatch(fetchPosts())
 
  expect(store.getState().posts.items).toHaveLength(1)
  expect(store.getState().posts.status).toBe("succeeded")
  vi.unstubAllGlobals()
})

store.dispatch(fetchPosts()) mengembalikan promise thunk. Setelah resolve, state bisa diperiksa langsung. Tambahkan skenario kedua dengan mockRejectedValue untuk memastikan status failed pada lifecycle rejected — keduanya mencakup seluruh lifecycle pending, fulfilled, dan rejected.

Testing Komponen dengan Store Uji

Render dengan Provider

Komponen Redux diuji dengan membungkusnya dalam Provider berisi test store:

JSsrc/features/posts/PostList.test.tsx
import { render, screen, waitFor } from "@testing-library/react"
import { Provider } from "react-redux"
import { createTestStore } from "../../test/store"
import PostList from "./PostList"
 
it("menampilkan daftar post dari store", () => {
  const store = createTestStore({
    posts: { items: [{ id: 1, title: "Post Satu" }], status: "succeeded" },
  })
 
  render(
    <Provider store={store}>
      <PostList />
    </Provider>,
  )
 
  expect(screen.getByText("Post Satu")).toBeInTheDocument()
})

Karena createTestStore menerima preloaded state, test bisa menanamkan data langsung tanpa menunggu fetch. Untuk komponen yang memakai hook query, gunakan waitFor menunggu cache terisi; store uji tetap memakai middleware default RTK — termasuk middleware RTK Query — sehingga hook query berjalan persis seperti di produksi.

Integrasi MSW untuk Mock API

Mendefinisikan Handler

MSW mem-mock di level jaringan: request /api/... ditangkap dan dijawab handler, bukan memblokir fetch per test. Definisikan handler sekali untuk seluruh suite:

JSsrc/test/mocks/handlers.ts
import { http, HttpResponse } from "msw"
 
export const handlers = [
  http.get("*/api/posts", () => {
    return HttpResponse.json([
      { id: 1, title: "Post MSW" },
      { id: 2, title: "Post Kedua" },
    ])
  }),
  http.post("*/api/posts", async ({ request }) => {
    const body = await request.json()
    return HttpResponse.json({ id: 3, ...body }, { status: 201 })
  }),
]

Handler berjalan untuk seluruh URL yang cocok — tak perlu menyentuh kode aplikasi sama sekali. MSW bekerja untuk fetch, axios, maupun graphql-request.

Menyalakan Server MSW

Aktifkan server di setup test:

JSsrc/test/setup.ts
import { setupServer } from "msw/node"
import { handlers } from "./mocks/handlers"
 
export const server = setupServer(...handlers)
 
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

Setelah setup ini, thunk maupun hook query yang melakukan request /api/... akan mendapatkan respons MSW. Kombinasikan dengan component test, maka seluruh lapisan — dari komponen sampai store — teruji tanpa server nyata.

Warning

Jangan campur mock fetch manual dan MSW dalam satu test file. Pilih satu strategi per lapisan: mock fetch untuk unit test thunk, MSW untuk integration dan component test agar perilaku jaringan lebih realistis.

Penutup

Testing Redux terasa ringan karena arsitekturnya yang murni. Reducer diuji sebagai fungsi biasa, thunk diuji dengan mem-mock fetch terhadap test store, dan komponen diuji dengan renderer yang dibungkus Provider. MSW menutup lapisan terluar dengan mem-mock API secara realistis, sehingga seluruh stack — action, reducer, store, komponen — dapat diverifikasi tanpa server.

Inti yang harus dibawa pulang:

  • Buat createTestStore agar tiap test memiliki state isolasi.
  • Reducer diuji sebagai fungsi murni dengan state dan action eksplisit.
  • Thunk diuji dengan mem-mock fetch dan memeriksa state setelah dispatch selesai.
  • Komponen di-render dengan Provider dan test store; gunakan waitFor untuk query.
  • MSW mem-mock API di level jaringan dengan handler HTTP.
  • Gunakan mock fetch untuk unit test dan MSW untuk integration test.

Episode 19 berikutnya membahas debugging — kalian akan memanfaatkan Redux DevTools untuk time-travel debugging, action trace, dan state diff, lalu memecahkan masalah umum seperti selector yang membuat object baru, Immer error, stale state, dan masalah hydration SSR.