> ## Documentation Index
> Fetch the complete documentation index at: https://codegeninc-codegen-bot-sdk-docs-2-0.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrating from unittest to pytest

> Learn how to migrate unittest test suites to pytest using Codegen

Migrating from [unittest](https://docs.python.org/3/library/unittest.html) to [pytest](https://docs.pytest.org/) involves converting test classes and assertions to pytest's more modern and concise style. This guide will walk you through using Codegen to automate this migration.

<Info>
  You can find the complete example code in our [examples repository](https://github.com/codegen-sh/codegen-sdk/tree/develop/codegen-examples/examples/unittest_to_pytest).
</Info>

## Overview

The migration process involves four main steps:

1. Converting test class inheritance and setup/teardown methods
2. Updating assertions to pytest style
3. Converting test discovery patterns
4. Modernizing fixture usage

Let's walk through each step using Codegen.

## Step 1: Convert Test Classes and Setup Methods

The first step is to convert unittest's class-based tests to pytest's function-based style. This includes:

* Removing `unittest.TestCase` inheritance
* Converting `setUp` and `tearDown` methods to fixtures
* Updating class-level setup methods

```python
# From:
class TestUsers(unittest.TestCase):
    def setUp(self):
        self.db = setup_test_db()

    def tearDown(self):
        self.db.cleanup()

    def test_create_user(self):
        user = self.db.create_user("test")
        self.assertEqual(user.name, "test")

# To:
import pytest

@pytest.fixture
def db():
    db = setup_test_db()
    yield db
    db.cleanup()

def test_create_user(db):
    user = db.create_user("test")
    assert user.name == "test"
```

## Step 2: Update Assertions

Next, we'll convert unittest's assertion methods to pytest's plain assert statements:

```python
# From:
def test_user_validation(self):
    self.assertTrue(is_valid_email("user@example.com"))
    self.assertFalse(is_valid_email("invalid"))
    self.assertEqual(get_user_count(), 0)
    self.assertIn("admin", get_roles())
    self.assertRaises(ValueError, parse_user_id, "invalid")

# To:
def test_user_validation():
    assert is_valid_email("user@example.com")
    assert not is_valid_email("invalid")
    assert get_user_count() == 0
    assert "admin" in get_roles()
    with pytest.raises(ValueError):
        parse_user_id("invalid")
```

## Step 3: Update Test Discovery

pytest uses a different test discovery pattern than unittest. We'll update the test file names and patterns:

```python
# From:
if __name__ == '__main__':
    unittest.main()

# To:
# Remove the unittest.main() block entirely
# Rename test files to test_*.py or *_test.py
```

## Step 4: Modernize Fixture Usage

Finally, we'll update how test dependencies are managed using pytest's powerful fixture system:

```python
# From:
class TestDatabase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.db_conn = create_test_db()

    def setUp(self):
        self.transaction = self.db_conn.begin()

    def tearDown(self):
        self.transaction.rollback()

# To:
@pytest.fixture(scope="session")
def db_conn():
    return create_test_db()

@pytest.fixture
def transaction(db_conn):
    transaction = db_conn.begin()
    yield transaction
    transaction.rollback()
```

## Common Patterns

Here are some common patterns you'll encounter when migrating to pytest:

1. **Parameterized Tests**

```python
# From:
def test_validation(self):
    test_cases = [("valid@email.com", True), ("invalid", False)]
    for email, expected in test_cases:
        with self.subTest(email=email):
            self.assertEqual(is_valid_email(email), expected)

# To:
@pytest.mark.parametrize("email,expected", [
    ("valid@email.com", True),
    ("invalid", False)
])
def test_validation(email, expected):
    assert is_valid_email(email) == expected
```

2. **Exception Testing**

```python
# From:
def test_exceptions(self):
    self.assertRaises(ValueError, process_data, None)
    with self.assertRaises(TypeError):
        process_data(123)

# To:
def test_exceptions():
    with pytest.raises(ValueError):
        process_data(None)
    with pytest.raises(TypeError):
        process_data(123)
```

3. **Temporary Resources**

```python
# From:
def setUp(self):
    self.temp_dir = tempfile.mkdtemp()

def tearDown(self):
    shutil.rmtree(self.temp_dir)

# To:
@pytest.fixture
def temp_dir():
    dir = tempfile.mkdtemp()
    yield dir
    shutil.rmtree(dir)
```

## Tips and Notes

1. pytest fixtures are more flexible than unittest's setup/teardown methods:

   * They can be shared across test files
   * They support different scopes (function, class, module, session)
   * They can be parameterized

2. pytest's assertion introspection provides better error messages by default:

   ```python
   # pytest shows a detailed comparison
   assert result == expected
   ```

3. You can gradually migrate to pytest:

   * pytest can run unittest-style tests
   * Convert one test file at a time
   * Start with assertion style updates before moving to fixtures

4. Consider using pytest's built-in fixtures:
   * `tmp_path` for temporary directories
   * `capsys` for capturing stdout/stderr
   * `monkeypatch` for modifying objects
   * `caplog` for capturing log messages
