Problem Statement
Compare different test automation frameworks for unit, integration, and E2E testing. Include JUnit/TestNG, Jest/Mocha, pytest, Selenium, Cypress, and Playwright with examples.
Explanation
Unit testing frameworks vary by language but share common patterns. JUnit 5 (Java) provides annotations, assertions, and lifecycle management:
```java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
@Test
void testAddition() {
Calculator calc = new Calculator();
assertEquals(5, calc.add(2, 3));
}
@Test
void testDivisionByZero() {
assertThrows(ArithmeticException.class,
() -> new Calculator().divide(10, 0));
}
}
```
Jest (JavaScript) offers zero-config setup, mocking, and snapshot testing:
```javascript
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
test('user creation', async () => {
const user = await createUser({ name: 'John' });
expect(user).toHaveProperty('id');
expect(user.name).toBe('John');
});
describe('Calculator', () => {
beforeEach(() => {
// Setup
});
test('multiplication', () => {
expect(multiply(2, 3)).toBe(6);
});
});
```
pytest (Python) uses simple assertions and fixtures:
```python
import pytest
def test_addition():
assert add(2, 3) == 5
def test_division_by_zero():
with pytest.raises(ZeroDivisionError):
divide(10, 0)
@pytest.mark.parametrize("input,expected", [
(2, 4),
(3, 9),
(4, 16)
])
def test_square(input, expected):
assert square(input) == expected
@pytest.fixture
def database():
db = Database()
yield db
db.cleanup()
```
Integration testing uses Testcontainers for real dependencies:
```java
@Container
private static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:13");
@Test
void testUserRepository() {
UserRepository repo = new UserRepository(postgres.getJdbcUrl());
User user = repo.save(new User("John"));
assertNotNull(user.getId());
}
```
E2E framework comparison:
Selenium (mature, cross-browser):
```python
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get("https://example.com/login")
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "username"))
)
driver.find_element(By.ID, "username").send_keys("testuser")
driver.find_element(By.ID, "password").send_keys("password123")
driver.find_element(By.ID, "submit").click()
assert "Dashboard" in driver.title
driver.quit()
```
Pros: mature, multi-language, supports all browsers. Cons: verbose, requires WebDriver setup, slower, complex wait management.
Cypress (modern, developer-friendly):
```javascript
describe('User Login', () => {
beforeEach(() => {
cy.visit('/login');
});
it('should login successfully', () => {
cy.get('[data-test="username"]').type('testuser');
cy.get('[data-test="password"]').type('password123');
cy.get('[data-test="submit"]').click();
cy.url().should('include', '/dashboard');
cy.get('h1').should('contain', 'Welcome');
});
it('should show error on invalid credentials', () => {
cy.get('[data-test="username"]').type('invalid');
cy.get('[data-test="password"]').type('wrong');
cy.get('[data-test="submit"]').click();
cy.get('[data-test="error"]').should('be.visible');
});
});
```
Pros: automatic waiting, time-travel debugging, network stubbing, great DX. Cons: limited cross-browser support initially (improving), runs in browser context.
Playwright (newest, powerful):
```javascript
const { test, expect } = require('@playwright/test');
test.describe('User Authentication', () => {
test('successful login', async ({ page }) => {
await page.goto('https://example.com/login');
await page.fill('[data-test="username"]', 'testuser');
await page.fill('[data-test="password"]', 'password123');
await page.click('[data-test="submit"]');
await expect(page).toHaveURL(/.*dashboard/);
await expect(page.locator('h1')).toContainText('Welcome');
});
test('failed login', async ({ page }) => {
await page.goto('https://example.com/login');
await page.fill('[data-test="username"]', 'invalid');
await page.fill('[data-test="password"]', 'wrong');
await page.click('[data-test="submit"]');
await expect(page.locator('[data-test="error"]')).toBeVisible();
});
test('mobile login', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto('https://example.com/login');
// Test mobile-specific behavior
});
});
```
Pros: cross-browser (Chromium, Firefox, WebKit), auto-waiting, network interception, mobile emulation, parallel execution, trace viewer. Cons: newer ecosystem.
Framework selection criteria: language alignment (Jest for Node.js, JUnit for Java), test type requirements, speed needs, cross-browser support, team expertise, ecosystem maturity. Understanding frameworks enables choosing appropriate tools for testing strategy.