🎯 Ejemplos recomendados
Balanced sample collections from various categories for you to explore
Ejemplos Mock Service Worker
Ejemplos completos de MSW (Mock Service Worker) cubriendo mocking de API, GraphQL, mocking de WebSocket y patrones avanzados de manejo de solicitudes
💻 Basics de Mocking REST API javascript
🟢 simple
⭐⭐
Mocking fundamental de REST API cubriendo operaciones GET, POST, PUT, DELETE y manejo de respuestas
⏱️ 25 min
🏷️ msw, api mocking, rest, testing
Prerequisites:
JavaScript, REST API, HTTP methods, Testing basics
// Mock Service Worker - REST API Basics
// File: src/mocks/handlers.js
import { rest } from 'msw'
// 1. Basic GET request handler
export const handlers = [
// Get user by ID
rest.get('/api/users/:id', (req, res, ctx) => {
const { id } = req.params
// Mock user data
const users = {
'1': { id: 1, name: 'John Doe', email: '[email protected]', role: 'user' },
'2': { id: 2, name: 'Jane Smith', email: '[email protected]', role: 'admin' },
'3': { id: 3, name: 'Bob Johnson', email: '[email protected]', role: 'user' }
}
const user = users[id]
if (user) {
return res(
ctx.status(200),
ctx.json(user)
)
}
return res(
ctx.status(404),
ctx.json({ error: 'User not found' })
)
}),
// Get all users with query parameters
rest.get('/api/users', (req, res, ctx) => {
const page = parseInt(req.url.searchParams.get('page') || '1')
const limit = parseInt(req.url.searchParams.get('limit') || '10')
const role = req.url.searchParams.get('role')
// Mock pagination
const allUsers = [
{ id: 1, name: 'John Doe', email: '[email protected]', role: 'user' },
{ id: 2, name: 'Jane Smith', email: '[email protected]', role: 'admin' },
{ id: 3, name: 'Bob Johnson', email: '[email protected]', role: 'user' },
{ id: 4, name: 'Alice Brown', email: '[email protected]', role: 'user' },
{ id: 5, name: 'Charlie Wilson', email: '[email protected]', role: 'moderator' }
]
let filteredUsers = allUsers
if (role) {
filteredUsers = allUsers.filter(user => user.role === role)
}
const startIndex = (page - 1) * limit
const endIndex = startIndex + limit
const paginatedUsers = filteredUsers.slice(startIndex, endIndex)
return res(
ctx.status(200),
ctx.json({
users: paginatedUsers,
pagination: {
page,
limit,
total: filteredUsers.length,
totalPages: Math.ceil(filteredUsers.length / limit)
}
})
)
}),
// POST - Create new user
rest.post('/api/users', async (req, res, ctx) => {
const userData = await req.json()
// Validation
if (!userData.name || !userData.email) {
return res(
ctx.status(400),
ctx.json({ error: 'Name and email are required' })
)
}
// Check if email already exists
const existingEmails = ['[email protected]', '[email protected]', '[email protected]']
if (existingEmails.includes(userData.email)) {
return res(
ctx.status(409),
ctx.json({ error: 'Email already exists' })
)
}
// Mock user creation
const newUser = {
id: Date.now(),
...userData,
role: userData.role || 'user',
createdAt: new Date().toISOString()
}
return res(
ctx.status(201),
ctx.json(newUser)
)
}),
// PUT - Update user
rest.put('/api/users/:id', async (req, res, ctx) => {
const { id } = req.params
const updateData = await req.json()
// Mock existing user
const existingUser = {
id: parseInt(id),
name: 'John Doe',
email: '[email protected]',
role: 'user',
updatedAt: new Date().toISOString()
}
// Validate update data
if (updateData.email && !updateData.email.includes('@')) {
return res(
ctx.status(400),
ctx.json({ error: 'Invalid email format' })
)
}
const updatedUser = {
...existingUser,
...updateData,
updatedAt: new Date().toISOString()
}
return res(
ctx.status(200),
ctx.json(updatedUser)
)
}),
// DELETE - Remove user
rest.delete('/api/users/:id', (req, res, ctx) => {
const { id } = req.params
// Simulate user deletion
if (id === '1') {
return res(
ctx.status(204)
)
}
return res(
ctx.status(404),
ctx.json({ error: 'User not found' })
)
}),
// File upload handler
rest.post('/api/upload', async (req, res, ctx) => {
const contentType = req.headers.get('content-type')
if (!contentType || !contentType.includes('multipart/form-data')) {
return res(
ctx.status(400),
ctx.json({ error: 'File upload required' })
)
}
// Mock file processing
return res(
ctx.status(200),
ctx.json({
message: 'File uploaded successfully',
filename: 'example.jpg',
size: 1024000,
url: 'https://example.com/files/example.jpg',
uploadedAt: new Date().toISOString()
})
)
}),
// Error simulation handler
rest.get('/api/error-test', (req, res, ctx) => {
const errorType = req.url.searchParams.get('type') || 'server'
switch (errorType) {
case 'server':
return res(
ctx.status(500),
ctx.json({ error: 'Internal server error', code: 'INTERNAL_ERROR' })
)
case 'network':
return res.networkError('Network connection failed')
case 'timeout':
return res(ctx.delay(10000), ctx.status(200), ctx.json({}))
case 'unauthorized':
return res(
ctx.status(401),
ctx.json({ error: 'Unauthorized', message: 'Please login first' })
)
default:
return res(
ctx.status(404),
ctx.json({ error: 'Endpoint not found' })
)
}
})
]
// File: src/mocks/server.js
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
// Create server for Node.js environment (testing)
export const server = setupServer(...handlers)
// File: src/mocks/browser.js
import { setupWorker } from 'msw/browser'
import { handlers } from './handlers'
// Create worker for browser environment (development)
export const worker = setupWorker(...handlers)
// File: src/setupTests.js (for Jest)
import '@testing-library/jest-dom'
import { server } from './mocks/server'
// Establish API mocking before all tests
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
// Reset any request handlers that we may add during the tests
afterEach(() => server.resetHandlers())
// Clean up after the tests are finished
afterAll(() => server.close())
// File: src/index.js (development setup)
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { worker } from './mocks/browser'
// Start Mock Service Worker in development
if (process.env.NODE_ENV === 'development') {
worker.start({
onUnhandledRequest: 'warn',
serviceWorker: {
url: '/mockServiceWorker.js'
}
}).then(() => {
console.log('Mock Service Worker started successfully')
}).catch(error => {
console.error('Failed to start Mock Service Worker:', error)
})
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
)
// Usage Example Component
// File: src/components/UserManager.js
import React, { useState, useEffect } from 'react'
export default function UserManager() {
const [users, setUsers] = useState([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
useEffect(() => {
fetchUsers()
}, [])
const fetchUsers = async () => {
try {
setLoading(true)
setError(null)
const response = await fetch('/api/users')
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
setUsers(data.users || [])
} catch (error) {
setError(error.message)
} finally {
setLoading(false)
}
}
const createUser = async (userData) => {
try {
setLoading(true)
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
})
if (!response.ok) {
const errorData = await response.json()
throw new Error(errorData.error || 'Failed to create user')
}
const newUser = await response.json()
setUsers(prev => [...prev, newUser])
return newUser
} catch (error) {
setError(error.message)
throw error
} finally {
setLoading(false)
}
}
const updateUser = async (id, userData) => {
try {
setLoading(true)
const response = await fetch(`/api/users/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
})
if (!response.ok) {
const errorData = await response.json()
throw new Error(errorData.error || 'Failed to update user')
}
const updatedUser = await response.json()
setUsers(prev => prev.map(user =>
user.id === parseInt(id) ? updatedUser : user
))
return updatedUser
} catch (error) {
setError(error.message)
throw error
} finally {
setLoading(false)
}
}
const deleteUser = async (id) => {
try {
setLoading(true)
const response = await fetch(`/api/users/${id}`, {
method: 'DELETE'
})
if (!response.ok) {
throw new Error('Failed to delete user')
}
setUsers(prev => prev.filter(user => user.id !== parseInt(id)))
} catch (error) {
setError(error.message)
throw error
} finally {
setLoading(false)
}
}
if (loading && users.length === 0) {
return <div>Loading users...</div>
}
if (error) {
return <div>Error: {error}</div>
}
return (
<div>
<h1>User Manager</h1>
<button onClick={fetchUsers}>Refresh Users</button>
{users.map(user => (
<div key={user.id} style={{ marginBottom: '1rem', padding: '1rem', border: '1px solid #ccc' }}>
<h3>{user.name}</h3>
<p>Email: {user.email}</p>
<p>Role: {user.role}</p>
<button onClick={() => updateUser(user.id, { name: user.name + ' (Updated)' })}>
Update
</button>
<button onClick={() => deleteUser(user.id)} style={{ marginLeft: '0.5rem' }}>
Delete
</button>
</div>
))}
</div>
)
}
💻 Mocking GraphQL API javascript
🟡 intermediate
⭐⭐⭐⭐
Mocking completo de GraphQL incluyendo queries, mutations, subscriptions y relaciones de datos complejas
⏱️ 35 min
🏷️ msw, graphql, mocking, subscriptions
Prerequisites:
GraphQL, React Query/Apollo Client, JavaScript ES6+, Subscription concepts
// Mock Service Worker - GraphQL API Mocking
// File: src/mocks/graphqlHandlers.js
import { graphql, GraphQLWsLink } from 'msw'
import { graphql } from 'msw/node'
import { setupServer } from 'msw/node'
// Mock database
const mockDatabase = {
users: [
{ id: '1', name: 'John Doe', email: '[email protected]', posts: ['1', '3'] },
{ id: '2', name: 'Jane Smith', email: '[email protected]', posts: ['2'] },
{ id: '3', name: 'Bob Johnson', email: '[email protected]', posts: [] }
],
posts: [
{ id: '1', title: 'First Post', content: 'This is my first post', authorId: '1', tags: ['javascript', 'react'] },
{ id: '2', title: 'GraphQL Basics', content: 'Learning GraphQL', authorId: '2', tags: ['graphql', 'tutorial'] },
{ id: '3', title: 'Testing with MSW', content: 'How to mock APIs with MSW', authorId: '1', tags: ['testing', 'msw'] }
],
comments: [
{ id: '1', postId: '1', authorId: '2', content: 'Great post!', createdAt: '2024-01-01T00:00:00Z' },
{ id: '2', postId: '1', authorId: '3', content: 'Thanks for sharing', createdAt: '2024-01-02T00:00:00Z' }
]
}
// GraphQL Handlers
export const graphqlHandlers = [
// Query: Get all users
graphql.query('GetUsers', (req, res, ctx) => {
const { limit, offset, filter } = req.variables
let users = mockDatabase.users
// Apply filters
if (filter?.name) {
users = users.filter(user =>
user.name.toLowerCase().includes(filter.name.toLowerCase())
)
}
// Apply pagination
const limitNum = limit || 10
const offsetNum = offset || 0
const paginatedUsers = users.slice(offsetNum, offsetNum + limitNum)
return res(
ctx.data({
users: paginatedUsers.map(user => ({
...user,
posts: mockDatabase.posts.filter(post => user.posts.includes(post.id))
})),
usersCount: users.length
})
)
}),
// Query: Get user by ID
graphql.query('GetUser', (req, res, ctx) => {
const { id } = req.variables
const user = mockDatabase.users.find(u => u.id === id)
if (!user) {
return res(
ctx.errors([
{
message: 'User not found',
locations: [{ line: 1, column: 1 }],
path: ['user']
}
])
)
}
const userPosts = mockDatabase.posts.filter(post => user.posts.includes(post.id))
return res(
ctx.data({
user: {
...user,
posts: userPosts.map(post => ({
...post,
comments: mockDatabase.comments.filter(comment => comment.postId === post.id)
}))
}
})
)
}),
// Query: Get posts with optional filters
graphql.query('GetPosts', (req, res, ctx) => {
const { limit, offset, authorId, tags } = req.variables
let posts = mockDatabase.posts
// Filter by author
if (authorId) {
posts = posts.filter(post => post.authorId === authorId)
}
// Filter by tags
if (tags && tags.length > 0) {
posts = posts.filter(post =>
tags.some(tag => post.tags.includes(tag))
)
}
// Pagination
const limitNum = limit || 10
const offsetNum = offset || 0
const paginatedPosts = posts.slice(offsetNum, offsetNum + limitNum)
return res(
ctx.data({
posts: paginatedPosts.map(post => ({
...post,
author: mockDatabase.users.find(user => user.id === post.authorId),
comments: mockDatabase.comments.filter(comment => comment.postId === post.id)
})),
postsCount: posts.length
})
)
}),
// Query: Search posts
graphql.query('SearchPosts', (req, res, ctx) => {
const { query, limit = 10 } = req.variables
if (!query || query.trim() === '') {
return res(
ctx.data({
searchResults: [],
searchCount: 0
})
)
}
const searchTerm = query.toLowerCase()
const matchingPosts = mockDatabase.posts.filter(post =>
post.title.toLowerCase().includes(searchTerm) ||
post.content.toLowerCase().includes(searchTerm) ||
post.tags.some(tag => tag.toLowerCase().includes(searchTerm))
)
const results = matchingPosts.slice(0, limit)
return res(
ctx.data({
searchResults: results.map(post => ({
...post,
author: mockDatabase.users.find(user => user.id === post.authorId),
matchScore: calculateMatchScore(post, searchTerm)
})),
searchCount: matchingPosts.length
})
)
}),
// Mutation: Create user
graphql.mutation('CreateUser', async (req, res, ctx) => {
const { input } = req.variables
const { name, email } = input
// Validation
if (!name || !email) {
return res(
ctx.errors([
{
message: 'Name and email are required',
locations: [{ line: 1, column: 1 }],
path: ['createUser']
}
])
)
}
// Check for duplicate email
const existingUser = mockDatabase.users.find(u => u.email === email)
if (existingUser) {
return res(
ctx.errors([
{
message: 'Email already exists',
extensions: { code: 'DUPLICATE_EMAIL' }
}
])
)
}
// Create new user
const newUser = {
id: (mockDatabase.users.length + 1).toString(),
name,
email,
posts: [],
createdAt: new Date().toISOString()
}
mockDatabase.users.push(newUser)
return res(
ctx.delay(300), // Simulate network delay
ctx.data({
createUser: newUser
})
)
}),
// Mutation: Create post
graphql.mutation('CreatePost', async (req, res, ctx) => {
const { input } = req.variables
const { title, content, authorId, tags = [] } = input
// Validate author exists
const author = mockDatabase.users.find(u => u.id === authorId)
if (!author) {
return res(
ctx.errors([
{
message: 'Author not found',
extensions: { code: 'AUTHOR_NOT_FOUND' }
}
])
)
}
// Create new post
const newPost = {
id: (mockDatabase.posts.length + 1).toString(),
title,
content,
authorId,
tags,
createdAt: new Date().toISOString()
}
mockDatabase.posts.push(newPost)
// Add post to author's posts
author.posts.push(newPost.id)
return res(
ctx.delay(500), // Simulate network delay
ctx.data({
createPost: {
...newPost,
author
}
})
)
}),
// Mutation: Update post
graphql.mutation('UpdatePost', async (req, res, ctx) => {
const { id, input } = req.variables
const postIndex = mockDatabase.posts.findIndex(p => p.id === id)
if (postIndex === -1) {
return res(
ctx.errors([
{
message: 'Post not found',
extensions: { code: 'POST_NOT_FOUND' }
}
])
)
}
// Update post
const updatedPost = {
...mockDatabase.posts[postIndex],
...input,
updatedAt: new Date().toISOString()
}
mockDatabase.posts[postIndex] = updatedPost
const author = mockDatabase.users.find(u => u.id === updatedPost.authorId)
return res(
ctx.delay(200),
ctx.data({
updatePost: {
...updatedPost,
author
}
})
)
}),
// Mutation: Delete post
graphql.mutation('DeletePost', async (req, res, ctx) => {
const { id } = req.variables
const postIndex = mockDatabase.posts.findIndex(p => p.id === id)
if (postIndex === -1) {
return res(
ctx.errors([
{
message: 'Post not found',
extensions: { code: 'POST_NOT_FOUND' }
}
])
)
}
// Remove post from database
const deletedPost = mockDatabase.posts.splice(postIndex, 1)[0]
// Remove post ID from author's posts
const author = mockDatabase.users.find(u => u.id === deletedPost.authorId)
if (author) {
author.posts = author.posts.filter(postId => postId !== id)
}
// Remove related comments
mockDatabase.comments = mockDatabase.comments.filter(
comment => comment.postId !== id
)
return res(
ctx.delay(100),
ctx.data({
deletePost: {
id: deletedPost.id,
success: true
}
})
)
}),
// Mutation: Add comment
graphql.mutation('AddComment', async (req, res, ctx) => {
const { postId, content, authorId } = req.variables
// Validate post and author exist
const post = mockDatabase.posts.find(p => p.id === postId)
const author = mockDatabase.users.find(u => u.id === authorId)
if (!post) {
return res(
ctx.errors([
{
message: 'Post not found',
extensions: { code: 'POST_NOT_FOUND' }
}
])
)
}
if (!author) {
return res(
ctx.errors([
{
message: 'Author not found',
extensions: { code: 'AUTHOR_NOT_FOUND' }
}
])
)
}
// Create new comment
const newComment = {
id: (mockDatabase.comments.length + 1).toString(),
postId,
authorId,
content,
createdAt: new Date().toISOString()
}
mockDatabase.comments.push(newComment)
return res(
ctx.delay(150),
ctx.data({
addComment: {
...newComment,
author
}
})
)
})
]
// Helper function for search score calculation
function calculateMatchScore(post, searchTerm) {
let score = 0
const lowerSearchTerm = searchTerm.toLowerCase()
// Title matches get higher score
if (post.title.toLowerCase().includes(lowerSearchTerm)) {
score += 10
}
// Content matches
if (post.content.toLowerCase().includes(lowerSearchTerm)) {
score += 5
}
// Tag matches
post.tags.forEach(tag => {
if (tag.toLowerCase().includes(lowerSearchTerm)) {
score += 3
}
})
return score
}
// GraphQL Subscription handler (experimental)
export const subscriptionHandlers = [
graphql.operation('PostCreated', (req, res, ctx) => {
// This simulates a real-time subscription
// In a real scenario, you'd use WebSocket connections
return res(
ctx.data({
postCreated: {
id: 'new-post-id',
title: 'New Post Created',
content: 'This is a newly created post',
authorId: '1',
createdAt: new Date().toISOString()
}
})
)
})
]
// Setup server for GraphQL
export const graphqlServer = setupServer(...graphqlHandlers)
// File: src/mocks/graphqlServer.js
import { setupServer } from 'msw/node'
import { graphqlHandlers } from './graphqlHandlers'
export const server = setupServer(...graphqlHandlers)
// File: src/mocks/graphqlBrowser.js
import { setupWorker } from 'msw/browser'
import { graphqlHandlers } from './graphqlHandlers'
export const worker = setupWorker(...graphqlHandlers)
// Usage Example - React Component
// File: src/components/PostManager.js
import React, { useState, useEffect } from 'react'
import { useQuery, useMutation, useSubscription } from '@apollo/client'
import { gql } from '@apollo/client'
const GET_POSTS = gql`
query GetPosts($limit: Int, $offset: Int, $authorId: ID, $tags: [String]) {
posts(limit: $limit, offset: $offset, authorId: $authorId, tags: $tags) {
id
title
content
createdAt
author {
id
name
email
}
comments {
id
content
createdAt
author {
name
}
}
tags
}
postsCount
}
`
const CREATE_POST = gql`
mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
id
title
content
createdAt
author {
id
name
email
}
tags
}
}
`
const POST_CREATED_SUBSCRIPTION = gql`
subscription PostCreated {
postCreated {
id
title
content
author {
name
}
createdAt
}
}
`
export default function PostManager() {
const [limit] = useState(10)
const [offset, setOffset] = useState(0)
const [filter, setFilter] = useState({})
const { data, loading, error, refetch } = useQuery(GET_POSTS, {
variables: { limit, offset, ...filter }
})
const [createPost, { loading: creating }] = useMutation(CREATE_POST, {
onCompleted: () => {
refetch()
}
})
// Subscription for real-time updates (if supported)
const { data: subscriptionData } = useSubscription(POST_CREATED_SUBSCRIPTION)
const handleCreatePost = async (postData) => {
try {
await createPost({
variables: {
input: {
...postData,
authorId: '1' // Mock current user
}
}
})
} catch (error) {
console.error('Error creating post:', error)
}
}
if (loading) return <div>Loading posts...</div>
if (error) return <div>Error: {error.message}</div>
return (
<div>
<h1>Post Manager</h1>
{/* New post creation form */}
<div style={{ marginBottom: '2rem', padding: '1rem', border: '1px solid #ccc' }}>
<h2>Create New Post</h2>
<form onSubmit={(e) => {
e.preventDefault()
const formData = new FormData(e.target)
handleCreatePost({
title: formData.get('title'),
content: formData.get('content'),
tags: formData.get('tags')?.split(',').map(tag => tag.trim())
})
}}>
<div style={{ marginBottom: '0.5rem' }}>
<input
name="title"
placeholder="Post title"
required
style={{ width: '100%', padding: '0.5rem' }}
/>
</div>
<div style={{ marginBottom: '0.5rem' }}>
<textarea
name="content"
placeholder="Post content"
required
style={{ width: '100%', padding: '0.5rem', minHeight: '100px' }}
/>
</div>
<div style={{ marginBottom: '0.5rem' }}>
<input
name="tags"
placeholder="Tags (comma-separated)"
style={{ width: '100%', padding: '0.5rem' }}
/>
</div>
<button
type="submit"
disabled={creating}
style={{ padding: '0.5rem 1rem', backgroundColor: '#0070f3', color: 'white' }}
>
{creating ? 'Creating...' : 'Create Post'}
</button>
</form>
</div>
{/* Filters */}
<div style={{ marginBottom: '1rem' }}>
<input
placeholder="Filter by author ID"
onChange={(e) => setFilter(prev => ({
...prev,
authorId: e.target.value || undefined
}))}
style={{ marginRight: '0.5rem', padding: '0.25rem' }}
/>
<input
placeholder="Filter by tags (comma-separated)"
onChange={(e) => setFilter(prev => ({
...prev,
tags: e.target.value ? e.target.value.split(',').map(tag => tag.trim()) : undefined
}))}
style={{ padding: '0.25rem' }}
/>
</div>
{/* Posts list */}
<div>
<h2>Posts ({data?.postsCount || 0})</h2>
{data?.posts?.map(post => (
<div key={post.id} style={{ marginBottom: '1rem', padding: '1rem', border: '1px solid #ddd' }}>
<h3>{post.title}</h3>
<p>By: {post.author?.name}</p>
<p>{post.content}</p>
<div style={{ fontSize: '0.8rem', color: '#666' }}>
Tags: {post.tags?.join(', ') || 'None'} |
Created: {new Date(post.createdAt).toLocaleDateString()}
</div>
{/* Comments */}
<div style={{ marginTop: '1rem' }}>
<h4>Comments ({post.comments?.length || 0})</h4>
{post.comments?.map(comment => (
<div key={comment.id} style={{
marginLeft: '1rem',
padding: '0.5rem',
backgroundColor: '#f5f5f5',
borderRadius: '4px',
marginBottom: '0.5rem'
}}>
<strong>{comment.author?.name}:</strong> {comment.content}
<div style={{ fontSize: '0.7rem', color: '#999' }}>
{new Date(comment.createdAt).toLocaleDateString()}
</div>
</div>
))}
</div>
</div>
))}
</div>
{/* Pagination */}
<div style={{ marginTop: '2rem', textAlign: 'center' }}>
<button
onClick={() => setOffset(prev => Math.max(0, prev - limit))}
disabled={offset === 0}
style={{ marginRight: '1rem', padding: '0.5rem 1rem' }}
>
Previous
</button>
<span>Page {Math.floor(offset / limit) + 1}</span>
<button
onClick={() => setOffset(prev => prev + limit)}
disabled={!data?.posts || data.posts.length < limit}
style={{ marginLeft: '1rem', padding: '0.5rem 1rem' }}
>
Next
</button>
</div>
{/* Real-time updates */}
{subscriptionData?.postCreated && (
<div style={{
position: 'fixed',
bottom: '1rem',
right: '1rem',
padding: '1rem',
backgroundColor: '#4CAF50',
color: 'white',
borderRadius: '4px'
}}>
<strong>New Post Alert!</strong><br/>
"{subscriptionData.postCreated.title}" by {subscriptionData.postCreated.author?.name}
</div>
)}
</div>
)
}
💻 Patrones Avanzados de MSW javascript
🔴 complex
⭐⭐⭐⭐⭐
Técnicas avanzadas de MSW incluyendo respuestas dinámicas, mocking de WebSocket, flujos de autenticación y pruebas de rendimiento
⏱️ 45 min
🏷️ msw, advanced, authentication, websockets, performance
Prerequisites:
MSW basics, JavaScript advanced, WebSocket concepts, Authentication flows, Performance testing
// Advanced Mock Service Worker Patterns
// File: src/mocks/advancedHandlers.js
import { rest, graphql, WebSocketLink } from 'msw'
import { setupServer } from 'msw/node'
// 1. Dynamic Response Generation
export const dynamicHandlers = [
// Dynamic user generator
rest.get('/api/dynamic-users/:count', (req, res, ctx) => {
const count = parseInt(req.params.count) || 10
const seed = req.url.searchParams.get('seed') || 'default'
// Seeded random number generator for consistent responses
const random = createSeededRandom(seed)
const users = Array.from({ length: count }, (_, index) => ({
id: index + 1,
name: generateRandomName(random),
email: `user${index + 1}@${generateRandomDomain(random)}`,
age: Math.floor(random() * 50) + 18,
avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${seed}-${index}`,
bio: generateRandomBio(random),
skills: generateRandomSkills(random),
createdAt: new Date(Date.now() - random() * 365 * 24 * 60 * 60 * 1000).toISOString()
}))
return res(
ctx.json({
users,
generated: new Date().toISOString(),
seed
})
)
}),
// Simulated real-time data updates
rest.get('/api/stocks/:symbol', (req, res, ctx) => {
const { symbol } = req.params
const initialPrice = Math.random() * 1000 + 50
// Generate realistic stock price fluctuations
const generatePriceHistory = () => {
let currentPrice = initialPrice
return Array.from({ length: 100 }, (_, index) => {
const change = (Math.random() - 0.5) * 10
currentPrice = Math.max(1, currentPrice + change)
return {
timestamp: Date.now() - (99 - index) * 60000, // Last 100 minutes
price: parseFloat(currentPrice.toFixed(2)),
volume: Math.floor(Math.random() * 1000000) + 10000
}
})
}
return res(
ctx.json({
symbol,
currentPrice: parseFloat(initialPrice.toFixed(2)),
change: parseFloat(((Math.random() - 0.5) * 20).toFixed(2)),
changePercent: parseFloat(((Math.random() - 0.5) * 5).toFixed(2)),
volume: Math.floor(Math.random() * 10000000) + 100000,
history: generatePriceHistory()
})
)
})
]
// 2. Authentication Flow Handlers
export const authHandlers = [
// Login endpoint
rest.post('/api/auth/login', async (req, res, ctx) => {
const { email, password } = await req.json()
// Simulate authentication delay
await ctx.delay(800)
// Mock user database
const users = [
{ email: '[email protected]', password: 'admin123', role: 'admin' },
{ email: '[email protected]', password: 'user123', role: 'user' },
{ email: '[email protected]', password: 'mod123', role: 'moderator' }
]
const user = users.find(u => u.email === email && u.password === password)
if (!user) {
return res(
ctx.status(401),
ctx.json({
error: 'Invalid credentials',
code: 'INVALID_CREDENTIALS'
})
)
}
// Generate JWT-like token
const token = generateMockToken(user)
const refreshToken = generateMockToken(user, 'refresh')
return res(
ctx.json({
user: {
id: user.email === '[email protected]' ? 1 :
user.email === '[email protected]' ? 2 : 3,
email: user.email,
role: user.role,
permissions: getPermissionsForRole(user.role)
},
tokens: {
accessToken: token,
refreshToken: refreshToken,
expiresIn: 3600
}
})
)
}),
// Token refresh
rest.post('/api/auth/refresh', async (req, res, ctx) => {
const { refreshToken } = await req.json()
if (!refreshToken) {
return res(
ctx.status(401),
ctx.json({ error: 'Refresh token required' })
)
}
// Validate refresh token (simplified)
try {
const payload = parseMockToken(refreshToken)
if (payload.type !== 'refresh') {
return res(
ctx.status(401),
ctx.json({ error: 'Invalid refresh token' })
)
}
// Generate new access token
const newAccessToken = generateMockToken(payload.user)
return res(
ctx.json({
accessToken: newAccessToken,
expiresIn: 3600
})
)
} catch (error) {
return res(
ctx.status(401),
ctx.json({ error: 'Invalid refresh token' })
)
}
}),
// Protected route middleware
rest.get('/api/protected/profile', (req, res, ctx) => {
const authHeader = req.headers.get('authorization')
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res(
ctx.status(401),
ctx.json({ error: 'Authorization required' })
)
}
const token = authHeader.slice(7)
try {
const payload = parseMockToken(token)
// Check token expiration
if (payload.exp < Date.now() / 1000) {
return res(
ctx.status(401),
ctx.json({ error: 'Token expired' })
)
}
// Return user profile
return res(
ctx.json({
user: payload.user,
permissions: getPermissionsForRole(payload.user.role)
})
)
} catch (error) {
return res(
ctx.status(401),
ctx.json({ error: 'Invalid token' })
)
}
})
]
// 3. WebSocket Mocking
export const wsHandlers = [
// WebSocket connection for real-time chat
rest.post('/api/chat/connect', (req, res, ctx) => {
return res(
ctx.json({
wsUrl: 'wss://mock-chat.example.com/ws',
userId: Math.random().toString(36).substr(2, 9),
sessionId: generateMockSessionId()
})
)
})
]
// WebSocket server simulation
export const mockWebSocketServer = {
connections: new Map(),
simulateConnection(userId, sessionId) {
return {
userId,
sessionId,
connected: true,
lastActivity: Date.now(),
messageCount: 0
}
},
simulateMessage(fromUserId, toUserId, content) {
return {
id: Math.random().toString(36).substr(2, 9),
from: fromUserId,
to: toUserId,
content,
timestamp: Date.now(),
status: 'delivered'
}
}
}
// 4. Performance Testing Handlers
export const performanceHandlers = [
// Simulate slow API responses
rest.get('/api/slow-endpoint', (req, res, ctx) => {
const delay = parseInt(req.url.searchParams.get('delay')) || 2000
return res(
ctx.delay(delay),
ctx.json({
message: `Response delayed by ${delay}ms`,
timestamp: Date.now()
})
)
}),
// Simulate large data responses
rest.get('/api/large-dataset', (req, res, ctx) => {
const size = parseInt(req.url.searchParams.get('size')) || 1000
const largeDataset = Array.from({ length: size }, (_, index) => ({
id: index + 1,
name: `Item ${index + 1}`,
description: `This is a detailed description for item ${index + 1}`,
metadata: {
category: `Category ${(index % 10) + 1}`,
tags: [`tag${index % 5}`, `tag${(index + 1) % 5}`],
rating: Math.random() * 5,
price: parseFloat((Math.random() * 1000).toFixed(2)),
inStock: Math.random() > 0.2,
createdAt: new Date(Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000).toISOString()
}
}))
return res(
ctx.json({
data: largeDataset,
total: largeDataset.length,
generatedAt: Date.now(),
size: JSON.stringify(largeDataset).length
})
)
}),
// Simulate concurrent requests stress test
rest.get('/api/stress-test', (req, res, ctx) => {
const iterations = parseInt(req.url.searchParams.get('iterations')) || 100
// Simulate CPU-intensive work
const result = []
for (let i = 0; i < iterations; i++) {
result.push({
iteration: i,
fibonacci: fibonacci(Math.min(i, 30)),
timestamp: Date.now()
})
}
return res(
ctx.delay(100),
ctx.json({
iterations,
results: result,
completedAt: Date.now()
})
)
})
]
// 5. Error Simulation Handlers
export const errorHandlers = [
// Configurable error scenarios
rest.get('/api/error-scenario/:type', (req, res, ctx) => {
const { type } = req.params
switch (type) {
case 'timeout':
return res(
ctx.delay(30000), // 30 second timeout
ctx.status(200),
ctx.json({ message: 'This will timeout' })
)
case 'network-error':
return res.networkError('Network connection failed')
case 'server-error':
return res(
ctx.status(500),
ctx.json({
error: 'Internal server error',
details: 'Something went terribly wrong',
timestamp: Date.now(),
requestId: generateRequestId()
})
)
case 'rate-limit':
return res(
ctx.status(429),
ctx.set('Retry-After', '60'),
ctx.json({
error: 'Rate limit exceeded',
message: 'Too many requests, please try again later',
retryAfter: 60
})
)
case 'maintenance':
return res(
ctx.status(503),
ctx.set('Retry-After', '3600'),
ctx.json({
error: 'Service unavailable',
message: 'We are currently under maintenance',
estimatedDowntime: '1 hour'
})
)
default:
return res(
ctx.status(400),
ctx.json({
error: 'Invalid error scenario',
validScenarios: ['timeout', 'network-error', 'server-error', 'rate-limit', 'maintenance']
})
)
}
}),
// Flaky endpoint that fails intermittently
rest.get('/api/flaky-endpoint', (req, res, ctx) => {
const failureRate = parseFloat(req.url.searchParams.get('failureRate')) || 0.3
if (Math.random() < failureRate) {
return res(
ctx.status(500),
ctx.json({
error: 'Random failure occurred',
failureRate,
timestamp: Date.now()
})
)
}
return res(
ctx.json({
message: 'Success!',
failureRate,
timestamp: Date.now()
})
)
})
]
// 6. Request/Response Interception and Modification
export const interceptionHandlers = [
// Modify request data
rest.post('/api/upload', async (req, res, ctx) => {
const originalBody = await req.json()
// Log and modify the request
console.log('Original request:', originalBody)
const modifiedBody = {
...originalBody,
processedAt: Date.now(),
enhanced: true
}
return res(
ctx.status(200),
ctx.json({
message: 'File uploaded successfully',
originalData: originalBody,
processedData: modifiedBody,
serverSide: true
})
)
}),
// Response transformation
rest.get('/api/data/*', (req, res, ctx) => {
// Let the original request go through
return fetch(req).then(response => {
if (!response.ok) {
return response
}
return response.json().then(data => {
// Transform the response
const transformedData = {
...data,
enhanced: true,
processedAt: Date.now(),
metadata: {
requestUrl: req.url,
method: req.method,
userAgent: req.headers.get('user-agent')
}
}
return res(
ctx.json(transformedData)
)
})
}).catch(() => {
return res(
ctx.status(500),
ctx.json({ error: 'Failed to fetch original data' })
)
})
})
]
// Utility Functions
function createSeededRandom(seed) {
let m = 0x80000000
let a = 1103515245
let c = 12345
let state = Math.abs(seed.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) || 1
return function() {
state = (a * state + c) % m
return state / m
}
}
function generateRandomName(random) {
const firstNames = ['John', 'Jane', 'Bob', 'Alice', 'Charlie', 'Diana', 'Edward', 'Fiona']
const lastNames = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis']
return firstNames[Math.floor(random() * firstNames.length)] + ' ' +
lastNames[Math.floor(random() * lastNames.length)]
}
function generateRandomDomain(random) {
const domains = ['example.com', 'test.org', 'demo.net', 'sample.io', 'mock.dev']
return domains[Math.floor(random() * domains.length)]
}
function generateRandomBio(random) {
const bios = [
'Software developer passionate about clean code',
'Tech enthusiast and lifelong learner',
'Building amazing web applications',
'Full-stack developer with creative flair',
'Open source contributor and mentor'
]
return bios[Math.floor(random() * bios.length)]
}
function generateRandomSkills(random) {
const allSkills = ['JavaScript', 'React', 'Node.js', 'Python', 'TypeScript', 'GraphQL', 'Docker', 'AWS']
const skillCount = Math.floor(random() * 4) + 2
const selectedSkills = []
for (let i = 0; i < skillCount; i++) {
const skill = allSkills[Math.floor(random() * allSkills.length)]
if (!selectedSkills.includes(skill)) {
selectedSkills.push(skill)
}
}
return selectedSkills
}
function generateMockToken(user, type = 'access') {
const header = btoa(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))
const payload = btoa(JSON.stringify({
sub: user.email,
role: user.role,
type,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + (type === 'refresh' ? 86400 : 3600),
user: { email: user.email, role: user.role }
}))
const signature = btoa('mock-signature')
return `${header}.${payload}.${signature}`
}
function parseMockToken(token) {
const [, payload] = token.split('.')
return JSON.parse(atob(payload))
}
function getPermissionsForRole(role) {
const permissions = {
admin: ['read', 'write', 'delete', 'manage_users'],
moderator: ['read', 'write', 'moderate'],
user: ['read', 'write_own']
}
return permissions[role] || ['read']
}
function generateMockSessionId() {
return 'session_' + Math.random().toString(36).substr(2, 9)
}
function generateRequestId() {
return 'req_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9)
}
function fibonacci(n) {
if (n <= 1) return n
let a = 0, b = 1
for (let i = 2; i <= n; i++) {
[a, b] = [b, a + b]
}
return b
}
// Export all handlers
export const allHandlers = [
...dynamicHandlers,
...authHandlers,
...wsHandlers,
...performanceHandlers,
...errorHandlers,
...interceptionHandlers
]
// Setup comprehensive server
export const advancedServer = setupServer(...allHandlers)
// File: src/mocks/advancedBrowser.js
import { setupWorker } from 'msw/browser'
import { allHandlers } from './advancedHandlers'
export const worker = setupWorker(...allHandlers)
// Usage Example - Advanced React Component
// File: src/components/AdvancedApiDemo.js
import React, { useState, useEffect, useCallback } from 'react'
export default function AdvancedApiDemo() {
const [dynamicUsers, setDynamicUsers] = useState([])
const [stockData, setStockData] = useState(null)
const [authUser, setAuthUser] = useState(null)
const [performanceData, setPerformanceData] = useState(null)
const [errorScenario, setErrorScenario] = useState('')
const [loading, setLoading] = useState(false)
// Generate dynamic users
const generateUsers = useCallback(async (count = 10, seed = 'default') => {
setLoading(true)
try {
const response = await fetch(`/api/dynamic-users/${count}?seed=${seed}`)
const data = await response.json()
setDynamicUsers(data.users)
} catch (error) {
console.error('Error generating users:', error)
} finally {
setLoading(false)
}
}, [])
// Fetch stock data
const fetchStockData = useCallback(async (symbol) => {
try {
const response = await fetch(`/api/stocks/${symbol}`)
const data = await response.json()
setStockData(data)
} catch (error) {
console.error('Error fetching stock data:', error)
}
}, [])
// Login simulation
const handleLogin = useCallback(async (email, password) => {
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
})
const data = await response.json()
if (response.ok) {
setAuthUser(data.user)
localStorage.setItem('accessToken', data.tokens.accessToken)
return data
} else {
throw new Error(data.error)
}
} catch (error) {
console.error('Login error:', error)
throw error
}
}, [])
// Fetch protected data
const fetchProtectedData = useCallback(async () => {
try {
const token = localStorage.getItem('accessToken')
const response = await fetch('/api/protected/profile', {
headers: { 'Authorization': `Bearer ${token}` }
})
const data = await response.json()
return data
} catch (error) {
console.error('Error fetching protected data:', error)
throw error
}
}, [])
// Performance test
const runPerformanceTest = useCallback(async (iterations = 100) => {
setLoading(true)
try {
const response = await fetch(`/api/stress-test?iterations=${iterations}`)
const data = await response.json()
setPerformanceData(data)
} catch (error) {
console.error('Performance test error:', error)
} finally {
setLoading(false)
}
}, [])
// Test error scenarios
const testErrorScenario = useCallback(async (type) => {
setLoading(true)
setErrorScenario(type)
try {
const response = await fetch(`/api/error-scenario/${type}`)
const data = await response.json()
console.log('Error scenario data:', data)
} catch (error) {
console.error(`Error scenario ${type}:`, error)
} finally {
setLoading(false)
setErrorScenario('')
}
}, [])
useEffect(() => {
// Initialize with some data
generateUsers(10, 'demo')
fetchStockData('AAPL')
}, [generateUsers, fetchStockData])
return (
<div style={{ padding: '2rem', fontFamily: 'Arial, sans-serif' }}>
<h1>Advanced API Demo</h1>
{/* Dynamic Users */}
<section style={{ marginBottom: '2rem' }}>
<h2>Dynamic User Generation</h2>
<div style={{ marginBottom: '1rem' }}>
<input
type="number"
placeholder="Count"
defaultValue="10"
style={{ marginRight: '0.5rem', padding: '0.5rem' }}
onChange={(e) => {
const count = parseInt(e.target.value)
const seed = 'dynamic-' + Date.now()
generateUsers(count, seed)
}}
/>
<button onClick={() => generateUsers(10, 'random-' + Date.now())}>
Generate New Users
</button>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: '1rem' }}>
{dynamicUsers.map(user => (
<div key={user.id} style={{ padding: '1rem', border: '1px solid #ddd', borderRadius: '8px' }}>
<img src={user.avatar} alt={user.name} style={{ width: '50px', height: '50px', borderRadius: '50%' }} />
<h4>{user.name}</h4>
<p>{user.email}</p>
<p>Age: {user.age}</p>
<p>{user.bio}</p>
<div>
{user.skills.map(skill => (
<span key={skill} style={{
display: 'inline-block',
padding: '0.25rem 0.5rem',
margin: '0.125rem',
backgroundColor: '#e0e0e0',
borderRadius: '12px',
fontSize: '0.75rem'
}}>
{skill}
</span>
))}
</div>
</div>
))}
</div>
</section>
{/* Stock Data */}
<section style={{ marginBottom: '2rem' }}>
<h2>Real-time Stock Simulation</h2>
<div style={{ marginBottom: '1rem' }}>
<input
type="text"
placeholder="Stock symbol"
defaultValue="AAPL"
onChange={(e) => fetchStockData(e.target.value)}
style={{ padding: '0.5rem', marginRight: '0.5rem' }}
/>
</div>
{stockData && (
<div style={{ padding: '1rem', backgroundColor: '#f5f5f5', borderRadius: '8px' }}>
<h3>{stockData.symbol}</h3>
<p style={{ fontSize: '1.5rem', fontWeight: 'bold' }}>
${stockData.currentPrice}
</p>
<p style={{ color: stockData.change >= 0 ? 'green' : 'red' }}>
{stockData.change >= 0 ? '+' : ''}{stockData.change} ({stockData.changePercent}%)
</p>
<p>Volume: {stockData.volume.toLocaleString()}</p>
</div>
)}
</section>
{/* Authentication Demo */}
<section style={{ marginBottom: '2rem' }}>
<h2>Authentication Flow</h2>
{!authUser ? (
<div>
<button onClick={() => handleLogin('[email protected]', 'admin123')}>
Login as Admin
</button>
<button onClick={() => handleLogin('[email protected]', 'user123')} style={{ marginLeft: '0.5rem' }}>
Login as User
</button>
</div>
) : (
<div>
<p>Logged in as: {authUser.email}</p>
<p>Role: {authUser.role}</p>
<p>Permissions: {authUser.permissions.join(', ')}</p>
<button onClick={async () => {
try {
await fetchProtectedData()
alert('Protected data accessed successfully!')
} catch (error) {
alert('Error accessing protected data: ' + error.message)
}
}}>
Test Protected Route
</button>
<button onClick={() => {
setAuthUser(null)
localStorage.removeItem('accessToken')
}} style={{ marginLeft: '0.5rem' }}>
Logout
</button>
</div>
)}
</section>
{/* Performance Testing */}
<section style={{ marginBottom: '2rem' }}>
<h2>Performance Testing</h2>
<div style={{ marginBottom: '1rem' }}>
<input
type="number"
placeholder="Iterations"
defaultValue="100"
style={{ marginRight: '0.5rem', padding: '0.5rem' }}
/>
<button onClick={() => {
const input = document.querySelector('input[placeholder="Iterations"]')
runPerformanceTest(parseInt(input.value))
}}>
Run Stress Test
</button>
</div>
{performanceData && (
<div style={{ padding: '1rem', backgroundColor: '#f0f0f0', borderRadius: '8px' }}>
<p>Completed {performanceData.iterations} iterations</p>
<p>Results processed in {performanceData.completedAt - performanceData.results[0].timestamp}ms</p>
<p>Final Fibonacci value: {performanceData.results[performanceData.results.length - 1]?.fibonacci}</p>
</div>
)}
</section>
{/* Error Scenarios */}
<section style={{ marginBottom: '2rem' }}>
<h2>Error Scenario Testing</h2>
<div style={{ marginBottom: '1rem' }}>
{['timeout', 'network-error', 'server-error', 'rate-limit', 'maintenance'].map(type => (
<button
key={type}
onClick={() => testErrorScenario(type)}
disabled={loading && errorScenario === type}
style={{ marginRight: '0.5rem', marginBottom: '0.5rem', padding: '0.5rem' }}
>
Test {type.replace('-', ' ')}
</button>
))}
</div>
{errorScenario && (
<div style={{ padding: '1rem', backgroundColor: '#fff3cd', borderRadius: '8px' }}>
<p>Testing error scenario: <strong>{errorScenario}</strong></p>
{loading && <p>Loading... (this may take a while for timeout scenarios)</p>}
</div>
)}
</section>
{loading && (
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<div style={{ backgroundColor: 'white', padding: '2rem', borderRadius: '8px' }}>
<p>Loading...</p>
</div>
</div>
)}
</div>
)
}