L10: Test Authentication Flow
Verify complete sign-in functionality
Let's thoroughly test the authentication system and ensure everything works perfectly! ✅
Complete Authentication Flow
Full user journey:
1. User visits app (not signed in)
↓
2. Navbar hidden, loading spinner shows
↓
3. No valid session found
↓
4. Loading completes, Navbar stays hidden
↓
5. User navigates to /sign-in
↓
6. Fills form and submits
↓
7. API validates credentials
↓
8. Success: Token + user data returned
↓
9. AuthContext state updates
↓
10. Navigate to home (/)
↓
11. Navbar appears, user is signed in!Testing Checklist
Test Initial Load (No Session)
-
Clear browser storage:
// Open DevTools Console localStorage.clear(); sessionStorage.clear(); document.cookie.split(";").forEach(c => { document.cookie = c.trim().split("=")[0] + '=;expires=' + new Date(0).toUTCString(); }); -
Refresh page
-
Observe:
- ✅ Loading spinner appears
- ✅ No Navbar visible
- ✅ Loading completes
- ✅ HomePage shows (public content)
Test Sign-In Navigation
- Click sign-in link/button
- Should navigate to
/sign-in - Observe:
- ✅ Sign-in page appears
- ✅ Form is visible
- ✅ Fields are empty
- ✅ No errors shown
Test Form Validation
-
Leave fields empty, click "Sign In"
-
Should see:
- ✅ "Email is required"
- ✅ "Password is required"
-
Enter invalid email:
bad-email -
Should see:
- ✅ "Please enter a valid email address"
-
Enter short password:
123 -
Should see:
- ✅ "Password must be at least 8 characters"
Test Invalid Credentials
-
Enter valid format but wrong credentials:
- Email:
wrong@example.com - Password:
wrongpassword123
- Email:
-
Click "Sign In"
-
Should see:
- ✅ Button shows "Signing in..."
- ✅ Button is disabled
- ✅ API error appears: "Invalid email or password"
- ✅ Still on sign-in page
- ✅ Form is still functional
Test Successful Sign-In
-
Enter valid credentials
-
Click "Sign In"
-
Should see:
- ✅ Button shows "Signing in..."
- ✅ Page redirects to
/ - ✅ Navbar appears
- ✅ User name/avatar in Navbar
- ✅ Can navigate to protected pages
-
Check React DevTools:
- ✅
userhas data - ✅
tokenhas JWT - ✅
isLoadingisfalse
- ✅
Test Session Persistence
- After signing in, refresh page (F5)
- Should see:
- ✅ Brief loading spinner
- ✅ Stays signed in
- ✅ Navbar visible
- ✅ User data preserved
- ✅ Still on same page
Test Navigation While Signed In
-
Navigate to different pages:
/→ HomePage/favorites→ FavoritesPage/sign-in→ SignInPage (should redirect)
-
All pages should:
- ✅ Show Navbar
- ✅ Have user data
- ✅ Allow sign-out
Test with Different Scenarios
New user signing in for first time:
Timeline:
0:00 - Visit site
0:01 - See homepage (no Navbar)
0:02 - Click "Sign In" button
0:03 - Fill credentials
0:04 - Submit form
0:05 - Redirect to homepage
0:06 - Navbar appears
0:07 - Explore site (signed in)Expected behavior:
// Initial state
user: null
token: null
isLoading: true
// After loading check
user: null
token: null
isLoading: false
// After sign-in
user: { name: "John", email: "...", ... }
token: "eyJhbG..."
isLoading: falseTest steps:
- Clear all storage (new user simulation)
- Visit homepage
- Navigate to
/sign-in - Enter valid credentials
- Submit form
- Verify redirect and Navbar appears
✅ First-time sign-in successful!
User who signed in before (has refresh token cookie):
Timeline:
0:00 - Visit site
0:01 - Loading spinner
0:02 - API fetches access token
0:03 - User data loaded
0:04 - Navbar appears
0:05 - User is signed in automatically!Expected behavior:
// Initial state
user: null
token: null
isLoading: true
// After token refresh
user: { name: "John", ... }
token: "eyJhbG..."
isLoading: falseTest steps:
- Sign in successfully
- Close browser (don't sign out)
- Reopen browser
- Visit homepage
- Should automatically sign in
✅ Session restored!
How long does session last?
Access token: 15 minutes (in memory)
Refresh token: 7-30 days (HTTP-only cookie)
After 15 min:
- Access token expired
- Refresh token still valid
- Next request triggers refresh
- New access token issuedUser returns after refresh token expired:
Timeline:
Day 30 - Last signed in
Day 31 - Refresh token expires
Day 32 - User visits site
0:01 - Loading spinner
0:02 - API returns 401 (token invalid)
0:03 - Loading completes
0:04 - No Navbar (not signed in)
0:05 - User must sign in againExpected behavior:
// Initial state
isLoading: true
// After failed token refresh
user: null
token: null
isLoading: false
// User sees:
- No Navbar
- Public content only
- "Sign In" button/linkTest steps:
- Manually delete refresh token cookie
- Refresh page
- Should see loading, then no Navbar
- Navigate to
/sign-in - Sign in again
✅ Expired session handled!
User experience:
User perspective:
"I haven't visited in a while, so I need to sign in again."
Not:
"The app is broken! I can't access anything!"Clear communication is key!
User has poor/no internet connection:
Scenario 1: Network error during sign-in
User submits form
↓
API call fails (network error)
↓
Catch block handles error
↓
Show error: "Network error. Please try again."
↓
Form remains functionalTest steps:
- Turn off internet
- Fill sign-in form
- Submit
- Should see network error message
- Turn on internet
- Submit again
- Should work
✅ Network error handled!
Scenario 2: Slow connection
User submits form
↓
Button shows "Signing in..."
↓
Request takes 5-10 seconds
↓
Button stays disabled
↓
Finally responds
↓
User signed in (or error shown)Test steps:
- Throttle network in DevTools:
- Network tab → Throttling → Slow 3G
- Submit form
- Observe button stays "Signing in..."
- Wait for response
✅ Slow connection handled!
Scenario 3: API timeout
const api = axios.create({
baseURL: 'https://v2.api.noroff.dev',
timeout: 10000, // 10 seconds
withCredentials: true,
});If request takes >10s, Axios throws timeout error.
Debugging Tools
Performance Testing
Count component renders:
function Navbar() {
const { user } = useAuth();
const renderCount = useRef(0);
useEffect(() => {
renderCount.current++;
console.log(`Navbar rendered ${renderCount.current} times`);
});
return <nav>...</nav>;
}Expected renders:
Initial: 1 render (user = null)
After sign-in: 1 render (user = data)
Total: 2 renders ✅Too many renders:
Initial: 1 render
After sign-in: 1 render
Random: 1 render (why?)
Random: 1 render (why?)
Total: 4 renders ❌Causes:
// ❌ New object every render
const value = { user, token, signIn: async () => {} };
// ✅ Memoize function
const signIn = useCallback(async (email, password) => {
// ...
}, []);
const value = useMemo(
() => ({ user, token, signIn }),
[user, token, signIn]
);Measure API performance:
const signIn = async (email, password) => {
const startTime = performance.now();
try {
const response = await api.post('/auth/login', { email, password });
const endTime = performance.now();
const duration = endTime - startTime;
console.log(`Sign-in took ${duration.toFixed(2)}ms`);
// ...
} catch (error) {
// ...
}
};Expected timings:
Fast connection: 100-500ms ✅
Normal connection: 500-2000ms ✅
Slow connection: 2000-5000ms ⚠️
Timeout: >10000ms ❌Optimization tips:
// 1. Add timeout
const api = axios.create({
timeout: 10000, // 10 seconds
});
// 2. Add retry logic
const signIn = async (email, password, retries = 3) => {
try {
return await api.post('/auth/login', { email, password });
} catch (error) {
if (retries > 0 && error.code === 'ECONNABORTED') {
console.log(`Retrying... (${retries} left)`);
return signIn(email, password, retries - 1);
}
throw error;
}
};Check for memory leaks:
// ❌ Potential leak: Missing cleanup
useEffect(() => {
const interval = setInterval(() => {
checkTokenExpiry();
}, 60000);
// Missing: return () => clearInterval(interval);
}, []);
// ✅ Proper cleanup
useEffect(() => {
const interval = setInterval(() => {
checkTokenExpiry();
}, 60000);
return () => clearInterval(interval);
}, []);Test for leaks:
- Open DevTools → Memory tab
- Take heap snapshot
- Sign in/out 10 times
- Take another heap snapshot
- Compare: Should be similar size
Common leak sources:
// Event listeners
window.addEventListener('resize', handler);
// Fix: window.removeEventListener('resize', handler);
// Timers
setTimeout(() => {}, 1000);
setInterval(() => {}, 1000);
// Fix: clearTimeout, clearInterval
// Subscriptions
const subscription = api.subscribe();
// Fix: subscription.unsubscribe();
// Async operations
const fetchData = async () => {
const data = await api.get('/data');
setState(data); // Component might be unmounted!
};
// Fix: Use AbortController or isMounted flagCheck impact on bundle size:
npm run buildCheck output:
dist/assets/index-abc123.js 142.34 kB
dist/assets/vendor-def456.js 456.78 kBReact Hook Form + Zod impact:
Before: ~120 kB
After: ~145 kB (+25 kB)Worth it for the functionality!
Optimize if needed:
// 1. Code splitting
const SignInForm = lazy(() => import('./SignInForm'));
// 2. Tree shaking
import { z } from 'zod'; // ✅ Imports only what you use
// 3. Dynamic imports
const loadZod = () => import('zod');What's Next?
In Lesson 11, we'll:
- Add Axios request interceptors
- Automatically attach token to all requests
- Handle token in API calls
- Protect API endpoints
✅ Lesson Complete! Authentication flow thoroughly tested and working!
Key Takeaways
- ✅ Test systematically through all user scenarios
- ✅ Use DevTools to inspect state and network
- ✅ Console logging helps debug issues
- ✅ Test edge cases (network errors, expired sessions)
- ✅ Session persistence works via refresh tokens
- ✅ Performance monitoring ensures smooth experience
- ✅ Memory leak prevention with proper cleanup
- ✅ First-time vs returning users have different flows
- ✅ Error messages guide users when things go wrong
- ✅ Loading states provide feedback during async operations