Writing Tests for Mastoscore

Each module in Mastoscore should have a corresponding test file in the tests/ directory. For example:

Test functions should follow this pattern:

  1. Arrange: Set up the test data and environment
  2. Act: Call the function being tested
  3. Assert: Verify the results

Example:

def test_read_config_with_valid_file():
    """Test reading a valid config file."""
    # Arrange
    with tempfile.NamedTemporaryFile(mode='w+', delete=False) as temp_file:
        temp_file.write("""
        [mastoscore]
        hashtag = testhashtag
        """)
        temp_file_path = temp_file.name

    # Act
    config = read_config(temp_file_path)

    # Assert
    assert config is not None
    assert config.get('mastoscore', 'hashtag') == 'testhashtag'

Using Fixtures

Fixtures provide a way to set up preconditions for tests. They are defined in conftest.py and can be used in any test file.

def test_analyse_function(sample_config, sample_journal_files, monkeypatch):
    """Test the analyse function."""
    # Use the sample_config fixture
    sample_config.set('mastoscore', 'journaldir', sample_journal_files)

    # Use monkeypatch to mock functions
    def mock_get_dates(config):
        # Mock implementation
        pass

    monkeypatch.setattr('mastoscore.analyse.get_dates', mock_get_dates)

    # Call the function and assert results
    results = analyse(sample_config)
    assert results is not None

Mocking

Use the pytest-mock fixture to mock external dependencies:

def test_fetch_dry_run(mocker, sample_config):
    """Test the fetch function in dry run mode."""
    # Mock the Tooter class
    mock_tooter = mocker.patch('mastoscore.fetch.Tooter')

    # Configure the mock
    mock_tooter.return_value.timeline_hashtag.return_value = [
        {"id": "1", "content": "Test toot"}
    ]

    # Call the function
    fetch(sample_config)

    # Assert the mock was called correctly
    mock_tooter.assert_called_once()

Testing File Operations

When testing functions that read or write files, use the temp_directory fixture:

def test_write_file(temp_directory):
    """Test writing a file."""
    # Create a path in the temporary directory
    file_path = os.path.join(temp_directory, "test_file.txt")

    # Call the function that writes to the file
    write_to_file(file_path, "test content")

    # Verify the file was written correctly
    with open(file_path, 'r') as f:
        content = f.read()

    assert content == "test content"

Testing Error Conditions

Don't forget to test error conditions and edge cases:

def test_read_config_with_missing_required_option():
    """Test reading a config file with missing required options."""
    # Create a config file with missing options
    with tempfile.NamedTemporaryFile(mode='w+', delete=False) as temp_file:
        temp_file.write("""
        [mastoscore]
        # Missing hashtag and other required options
        """)
        temp_file_path = temp_file.name

    # The function should return None for invalid config
    config = read_config(temp_file_path)
    assert config is None

Parameterized Tests

Use pytest's parametrize decorator to test multiple scenarios:

@pytest.mark.parametrize("hashtag,expected", [
    ("test", "test"),
    ("test-hashtag", "test-hashtag"),
    ("", None),  # Empty hashtag should return None
])
def test_validate_hashtag(hashtag, expected):
    """Test hashtag validation with different inputs."""
    result = validate_hashtag(hashtag)
    assert result == expected