A full-stack toy-swapping application. Parents and kids can list toys they no longer want and trade them with other users.
| Layer | Technology |
|---|---|
| Backend | Java 17, Spring Boot 4, Spring Data JPA, H2 (in-memory) |
| Frontend | React 19, TypeScript, Vite, React Router v7 |
| Backend tests | JUnit 5, MockMvc, Mockito, H2 |
| Frontend unit tests | Vitest, React Testing Library, MSW |
| End-to-end tests | Playwright (Chromium) |
| Mutation testing | PIT (pitest-maven) |
- Java 17+
- Node.js 18+
- npm
toyswap/
├── backend/ # Spring Boot application
│ └── src/
│ ├── main/java/com/example/toyswap/
│ │ ├── controller/ # REST controllers
│ │ ├── model/ # JPA entities (Item, Swapper)
│ │ └── repository/ # Spring Data repositories
│ └── test/ # Backend tests
├── frontend/ # React + Vite application
│ ├── e2e/ # Playwright end-to-end specs
│ └── src/
│ ├── api/ # fetch wrappers
│ ├── components/
│ ├── context/ # AuthContext
│ ├── pages/
│ ├── test/ # MSW handlers and Vitest setup
│ └── types/ # TypeScript interfaces
├── seed.mjs # Dev data seed script
└── start-test-env.sh # Builds and starts backend + frontend preview
cd backend
./mvnw spring-boot:runThe API will be available at http://localhost:8080/api.
The H2 console is available at http://localhost:8080/h2-console:
- JDBC URL:
jdbc:h2:mem:toyswapdb - Username:
sa - Password: (leave blank)
Note: The database is in-memory and is wiped on every restart. Use
seed.mjsto repopulate it.
In a separate terminal:
cd frontend
npm install
npm run devThe app will be available at http://localhost:3000. API calls are proxied to http://localhost:8080.
With the backend running, execute from the project root:
node seed.mjsThis creates 10 Bluey-character swappers (all with password password123) and 6 items each (60 items total). It is safe to run multiple times — existing swappers produce a warning and are skipped.
| Username | Name |
|---|---|
bluey_heeler |
Bluey Heeler |
bingo_heeler |
Bingo Heeler |
bandit_heeler |
Bandit Heeler |
chilli_heeler |
Chilli Heeler |
jack_wheeler |
Jack Wheeler |
chloe_heeler |
Chloe Heeler |
judo_malik |
Judo Malik |
mackenzie_dog |
Mackenzie Dog |
coco_parrot |
Coco Parrot |
calypso_teacher |
Calypso Teacher |
cd backend
./mvnw testThis runs all JUnit 5 tests. Reports are written to backend/target/surefire-reports/.
To run a single test class:
./mvnw test -Dtest=ItemControllerTestTo run a single test method:
./mvnw test -Dtest=ItemControllerTest#createItem_returnsCreated| File | Coverage |
|---|---|
ItemControllerTest |
All GET/POST/PUT/DELETE item endpoints via MockMvc |
SwapperControllerTest |
Account creation, login (success/failure), conflict handling |
ItemRepositoryTest |
JPA queries against a real H2 database (@Transactional rollback) |
SwapperRepositoryTest |
findByUsernameAndPassword happy and failure paths |
Mutation testing verifies that your tests actually catch bugs, not just that they pass.
cd backend
./mvnw test pitest:mutateThe HTML report is written to backend/target/pit-reports/index.html. Open it in a browser to see which mutants were killed vs. survived.
This can take 1–3 minutes depending on test count.
cd frontend
npm testRuns all *.test.tsx files once and exits. API calls are intercepted by MSW handlers defined in src/test/handlers.ts — no real backend needed.
To run in watch mode (re-runs on file save):
npm run test:watchTo run a single test file:
npx vitest run src/pages/LoginPage.test.tsxTo run tests matching a name pattern:
npx vitest run --reporter=verbose -t "shows error on 401"| File | Coverage |
|---|---|
components/ItemCard.test.tsx |
Rendering, badges, action button, onAction callback |
components/ProtectedRoute.test.tsx |
Redirects unauthenticated users to /login |
pages/LoginPage.test.tsx |
Login flow, 401 error message, loading state |
pages/CreateAccountPage.test.tsx |
Account creation flow, 409 conflict message |
pages/HomePage.test.tsx |
Item list, empty state, navigation buttons |
pages/AddItemPage.test.tsx |
Add item form submission |
pages/ItemSwapPage.test.tsx |
Displays other users' items, owner filter |
components/SwapModal.test.tsx |
Swap confirmation flow |
E2E tests run against the built frontend preview (localhost:4173) and live backend (localhost:8080). Both must be running.
# From the project root — builds and starts both servers, then leave running
./start-test-env.shIn a separate terminal:
cd frontend
npx playwright test# Terminal 1 — backend
cd backend && ./mvnw spring-boot:run
# Terminal 2 — frontend preview
cd frontend && npm run build && npm run preview
# Terminal 3 — run tests
cd frontend && npx playwright test# Run a specific spec file
npx playwright test e2e/auth.spec.ts
# Run in headed mode (watch the browser)
npx playwright test --headed
# Run a specific test by title
npx playwright test -g "login with valid credentials"
# Show the HTML report after a run
npx playwright show-report| File | Coverage |
|---|---|
e2e/auth.spec.ts |
Login, create account, invalid credentials, redirect when unauthenticated |
e2e/items.spec.ts |
Add a toy, appears on home page |
e2e/swap.spec.ts |
Complete a swap, modal close, items disappear after swap |
| Method | Path | Description |
|---|---|---|
GET |
/api/swappers |
List all swappers |
GET |
/api/swappers/{userId} |
Get swapper by ID |
POST |
/api/swappers |
Create account (201; 409 if userId already taken) |
POST |
/api/swappers/login |
Login (200 with Swapper; 401 on failure) |
PUT |
/api/swappers/{userId} |
Update swapper |
| Method | Path | Description |
|---|---|---|
GET |
/api/items |
All active items |
GET |
/api/items/{id} |
Item by ID |
GET |
/api/items/owner/{userId} |
Active items owned by a swapper |
GET |
/api/items/type/{type} |
Filter by type (toy, book, misc) |
GET |
/api/items/age/{ageLevel} |
Filter by age level (baby, crawler, toddler, child, kid) |
POST |
/api/items?ownerId={userId} |
Create item and assign owner |
PUT |
/api/items/{id}?ownerId={userId} |
Update item |
| Method | Path | Description |
|---|---|---|
POST |
/api/swaps |
Execute a swap — body: { offerItemId, requestItemId } |