Ever deployed code at 5 PM on a Friday, feeling confident, only to get that dreaded midnight call? We've all been there. But what if I told you there's a better way? Let's dive into how automated testing can save your weekends (and your sanity).
The Real Cost of Skipping Tests
Picture this: You're building a simple user authentication system. Seems straightforward, right?
class UserAuth {
async validatePassword(password: string): Promise<boolean> {
if (!password) return false;
// Some validation logic here
return password.length >= 8 && /[A-Z]/.test(password);
}
async loginUser(email: string, password: string): Promise<boolean> {
const isValid = await this.validatePassword(password);
if (!isValid) return false;
// Login logic here
return true;
}
}
π€ Pop Quiz: Can you spot potential issues in this code? What edge cases might we be missing?
Without proper testing, we might miss:
- Empty email validation
- Password complexity requirements
- SQL injection vulnerabilities
- Rate limiting concerns
The Testing Arsenal: Tools You Need
Before we dive deeper, let's look at the essential testing tools that will make your life easier:
1. Unit Testing
- Jest: The Swiss Army knife of testing
- Mocha: Flexible testing framework
- Vitest: Ultra-fast unit testing
2. End-to-End Testing
- Cypress: Modern web testing
- Playwright: Cross-browser testing
- Selenium: Traditional but powerful
3. Integration Testing
- Supertest: HTTP assertions
- TestCafe: No Selenium required
- Postman: API testing made easy
4. Performance Testing
- k6: Developer-centric load testing
- Artillery: Load and performance testing
- Apache JMeter: Comprehensive performance testing
π‘ Pro Tip: Start with Jest for unit tests and Cypress for E2E tests. Add others as needed.
Real-World Testing Scenarios
1. Authentication Flow Testing
describe('Authentication Flow', () => {
describe('Login Process', () => {
it('should handle rate limiting', async () => {
const auth = new UserAuth();
for (let i = 0; i < 5; i++) {
await auth.loginUser('test@email.com', 'wrong');
}
await expect(
auth.loginUser('test@email.com', 'correct')
).rejects.toThrow('Too many attempts');
});
it('should prevent timing attacks', async () => {
const auth = new UserAuth();
const start = process.hrtime();
await auth.loginUser('fake@email.com', 'wrongpass');
const end = process.hrtime(start);
// Should take same time regardless of email existence
expect(end[0]).toBeLessThan(1);
});
});
describe('Password Reset', () => {
it('should validate token expiration', async () => {
const auth = new UserAuth();
const token = await auth.generateResetToken('user@email.com');
// Fast-forward time by 1 hour
jest.advanceTimersByTime(3600000);
await expect(
auth.resetPassword(token, 'newPassword')
).rejects.toThrow('Token expired');
});
});
});
2. E2E Testing with Cypress
describe('User Journey', () => {
it('should complete signup and first task', () => {
cy.intercept('POST', '/api/signup').as('signupRequest');
// Visit signup page
cy.visit('/signup');
// Fill form
cy.get('[data-testid="name-input"]').type('John Doe');
cy.get('[data-testid="email-input"]').type('john@example.com');
cy.get('[data-testid="password-input"]').type('SecurePass123!');
// Submit and verify
cy.get('[data-testid="signup-button"]').click();
cy.wait('@signupRequest');
// Should redirect to dashboard
cy.url().should('include', '/dashboard');
// Create first task
cy.get('[data-testid="new-task"]').click();
cy.get('[data-testid="task-title"]').type('My First Task');
cy.get('[data-testid="save-task"]').click();
// Verify task creation
cy.contains('My First Task').should('be.visible');
});
});
3. API Integration Testing
describe('API Integration', () => {
it('should handle third-party API failures gracefully', async () => {
const paymentService = new PaymentService();
// Mock failed API response
jest.spyOn(paymentService, 'processPayment').mockRejectedValue(
new Error('Gateway timeout')
);
const order = new Order({
items: [{id: 1, quantity: 2}],
total: 50.00
});
// Should retry and fall back to backup provider
const result = await order.checkout();
expect(result.status).toBe('success');
expect(result.provider).toBe('backup-provider');
});
});
π€ Question Time: How do you handle flaky tests in your CI pipeline?
Advanced Testing Patterns
1. Snapshot Testing
it('should maintain consistent UI components', () => {
const button = render(<PrimaryButton label="Click me" />);
expect(button).toMatchSnapshot();
});
2. Property-Based Testing
import fc from 'fast-check';
test('string reversal is symmetric', () => {
fc.assert(
fc.property(fc.string(), str => {
expect(reverseString(reverseString(str))).toBe(str);
})
);
});
3. Contract Testing
const userContract = {
id: Joi.number().required(),
email: Joi.string().email().required(),
role: Joi.string().valid('user', 'admin')
};
describe('User API', () => {
it('should return data matching contract', async () => {
const response = await request(app).get('/api/user/1');
const validation = Joi.validate(response.body, userContract);
expect(validation.error).toBeNull();
});
});
Setting Up Your Testing Pipeline
# .github/workflows/test.yml
name: Test Suite
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: '18'
- name: Install Dependencies
run: npm ci
- name: Unit Tests
run: npm run test:unit
- name: Integration Tests
run: npm run test:integration
- name: E2E Tests
run: npm run test:e2e
- name: Upload Coverage
uses: codecov/codecov-action@v2
π― Challenge: Set up a pre-commit hook that runs tests and maintains a minimum coverage threshold!
Best Practices Checklist
β
Write tests before fixing bugs
β
Use data-testid for E2E test selectors
β
Implement retry logic for flaky tests
β
Maintain test isolation
β
Mock external dependencies
β
Use test doubles appropriately (stubs, spies, mocks)
β
Follow the AAA pattern (Arrange, Act, Assert)
Conclusion
Remember: Every untested function is a potential bug waiting to happen. But with automated testing, you're not just writing code β you're building confidence.
π― Final Challenge: Take your most critical piece of untested code and write at least three test cases for it today. Share your experience in the comments!
Top comments (0)