Skip to content

Fix fixtures not resetting when set to empty list#178

Merged
bittner merged 3 commits intomainfrom
copilot/fix-fixtures-reset
Nov 15, 2025
Merged

Fix fixtures not resetting when set to empty list#178
bittner merged 3 commits intomainfrom
copilot/fix-fixtures-reset

Conversation

Copy link
Contributor

Copilot AI commented Nov 14, 2025

Fix issue where fixtures are not reset between features when set to an empty list

Plan

  • Explore the repository and understand the issue
  • Create a test that demonstrates the bug (fixtures not resetting when context.fixtures = [])
  • Fix the bug in behave_django/environment.py setup_fixtures method
  • Verify the fix doesn't break existing tests
  • Update documentation if needed to match the new behavior
  • Run linting and all tests
  • Add warning about v2.0.0 automatic fixture reset to documentation
  • Fix ineffective test - replaced with proper feature-level test
  • Request code review

Changes Made

  1. Fixed the bug in behave_django/environment.py:

    • Changed setup_fixtures() method to always set fixtures and reset_sequences, even when they're empty
    • Previously: if getattr(context, 'fixtures', None): would skip setting fixtures when empty list was provided
    • Now: Always set fixtures with fixtures = getattr(context, 'fixtures', []) and unconditionally assign them
  2. Added proper test coverage:

    • Created two separate feature files to test fixture reset BETWEEN features (not scenarios):
      • fixture-reset-first.feature: Sets fixtures in before_feature
      • fixture-reset-second.feature: Sets context.fixtures = [] in before_feature
    • This properly tests the bug described in the issue where fixtures carry over between features
    • Removed the old ineffective test that only tested scenario-level reset (which worked even without the fix)
    • Updated tests/acceptance/environment.py to configure the new test features
  3. Removed workaround:

    • Removed the workaround in tests/acceptance/environment.py that used ['empty-fixture.json']
    • Now properly uses empty list [] to reset fixtures as intended
  4. Updated documentation:

    • Added warning box to docs/fixtures.rst about automatic fixture reset in v2.0.0
    • The warning explains that context.fixtures will be automatically reset after each scenario starting with v2.0.0

Test Verification

New tests properly fail when fix is reverted - Verified that fixture-reset-second.feature fails with "ASSERT FAILED: 1 != 0" when the fix is removed
New tests pass with the fix - Both feature files pass successfully
✅ All other acceptance tests pass (22 scenarios, 44 steps)
✅ Linting passes (ruff check and ruff format)
✅ Documentation builds successfully with new warning box

Why the Previous Test Was Ineffective

The original fixture-reset.feature tested fixture reset between SCENARIOS within the same feature by setting context.fixtures = [] in before_scenario(). However, the bug is specifically about setting context.fixtures = [] in before_feature() between different features. The scenario-level reset worked even without the fix because before_scenario() runs after before_feature(), so it would always override any feature-level fixtures.

Original prompt

This section details on the original issue you should resolve

<issue_title>Fixtures are not resetted between features</issue_title>
<issue_description>This problem is present in 1.7.0 and also in 1.8.0. It appears when I am running two features so that the first feature sets a fixture and the second one should have an empty fixture. For example, in environment.py one could do (as is in the documentation):

def before_feature(context, feature):
    if feature.name == 'First feature':
        context.fixtures = ['user-data.json']
    else:
        # Resetting fixtures, otherwise previously set fixtures carry
        # over to subsequent features.
        context.fixtures = []

But, even if the comments say otherwise, the previously set fixtures do carry over to the second fixture.

The reason is in environment.py file's setup_fixtures method:

    def setup_fixtures(self, context):
        """Set up fixtures."""
        if getattr(context, 'fixtures', None): # <- This is the problem!
            if django.VERSION >= (5, 2):
                context.test.__class__.fixtures = copy(context.fixtures)
            else:
                context.test.fixtures = copy(context.fixtures)

        if getattr(context, 'reset_sequences', None):
            if django.VERSION >= (5, 2):
                context.test.__class__.reset_sequences = context.reset_sequences
            else:
                context.test.reset_sequences = context.reset_sequences

Python considers empty list falsy, which means the if is skipped altogether, and the old value stays from the previous feature.

It would be better to handle it like this:

        fixtures = getattr(context, "fixtures", [])
        if django.VERSION >= (5, 2):
            context.test.__class__.fixtures = copy(fixtures)
        else:
            context.test.fixtures = copy(fixtures)

        reset_sequences = getattr(context, "reset_sequences", None)
        if django.VERSION >= (5, 2):
            context.test.__class__.reset_sequences = reset_sequences
        else:
            context.test.reset_sequences = reset_sequences

That way the fixtures are always set per setup_fixtures call, and even missing fixtures attribute means no fixtures.

Workaround with <=1.8.0 is to set an empty fixture file into the context.fixtures so that it is not considered False.

context.fixture = ["empty_fixture.json"]
```</issue_description>

<agent_instructions>Analyse the current code and @jrutila's proposed fix.

- Find out whether we already have a test case that needs to be extended or adapted to cover this bug, add a new test otherwise.
- Fix the bug, but refrain from adding comments to the code. Add the explanation to the PR, instead.
- Check the "Fixture Loading" chapter in our documentation and verify whether the content still makes sense and matches the implementation.</agent_instructions>

## Comments on the Issue (you are @copilot in this section)

<comments>
</comments>


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI changed the title [WIP] Fix fixtures not resetting between features Fix fixtures not resetting when set to empty list Nov 14, 2025
Copilot AI requested a review from bittner November 14, 2025 18:33
@bittner
Copy link
Member

bittner commented Nov 14, 2025

@copilot What is the reason for having to reset the fixtures, as described in the documentation? What would it take to change the behavior and make using fixtures thus more user-friendly?

Copy link
Contributor Author

Copilot AI commented Nov 14, 2025

@copilot What is the reason for having to reset the fixtures, as described in the documentation? What would it take to change the behavior and make using fixtures thus more user-friendly?

The reason fixtures need manual resetting is that behave reuses the same context object across scenarios and features. Any attribute set on context (like context.fixtures) persists unless explicitly cleared.

To make this more user-friendly, we could automatically clear context.fixtures after setup_fixtures() processes it. This would mean:

  1. User sets context.fixtures = ['foo.json'] in before_scenario()
  2. Our setup_fixtures() reads and applies it to context.test.fixtures
  3. We clear context.fixtures so it doesn't carry over

This would require a one-line change in setup_fixtures() after line 79:

fixtures = getattr(context, 'fixtures', [])
if django.VERSION >= (5, 2):
    context.test.__class__.fixtures = copy(fixtures)
else:
    context.test.fixtures = copy(fixtures)
del context.fixtures  # Auto-reset for next scenario

However, this could break existing code that:

  • Appends to context.fixtures in before_scenario() (like our test suite does)
  • Expects to read context.fixtures after it's been set

A safer approach might be to document this as a breaking change for v2.0, or add an opt-in setting. Would you like me to explore either approach?

@bittner
Copy link
Member

bittner commented Nov 14, 2025

A safer approach might be to document this as a breaking change for v2.0, or add an opt-in setting.

@copilot Add a warning box to the related documentation saying that starting with v2.0.0 the fixtures attribute will be reset automatically.

Copy link
Contributor Author

Copilot AI commented Nov 14, 2025

A safer approach might be to document this as a breaking change for v2.0, or add an opt-in setting.

@copilot Add a warning box to the related documentation saying that starting with v2.0.0 the fi...

Added a warning box to docs/fixtures.rst in commit 80d71c1. The warning appears after the section explaining the current manual reset behavior and notifies users that starting with v2.0.0, context.fixtures will be automatically reset after each scenario.

@bittner
Copy link
Member

bittner commented Nov 15, 2025

@copilot I appears that the tests are ineffective. When I undo the changes to the environment module, the test suite passes successfully.

Please fix this! We should be able to reproduce the problem with a test before changing the code.

Copy link
Contributor Author

Copilot AI commented Nov 15, 2025

@copilot I appears that the tests are ineffective. When I undo the changes to the environment module, the test suite passes successfully.

Please fix this! We should be able to reproduce the probl...

Fixed in commit 58f1ce0. The original test was ineffective because it tested fixture reset between scenarios (using before_scenario()), but the bug is specifically about reset between features (using before_feature()).

Created two separate feature files that properly test the issue:

  • fixture-reset-first.feature: Sets fixtures in before_feature()
  • fixture-reset-second.feature: Sets context.fixtures = [] in before_feature()

Verified the new tests fail when the fix is reverted (showing "ASSERT FAILED: 1 != 0") and pass with the fix in place.

@bittner
Copy link
Member

bittner commented Nov 15, 2025

@copilot This appears to be a problem only with Django 5.2. If I see this correctly, all tests with older Django versions pass - without the fix applied. Can you explain why?

Copy link
Contributor Author

Copilot AI commented Nov 15, 2025

@copilot This appears to be a problem only with Django 5.2. If I see this correctly, all tests with older Django versions pass - without the fix applied. Can you explain why?

The bug only affects Django 5.2+ because of how fixtures are stored:

Django < 5.2: Uses instance attributes (context.test.fixtures)

  • Each scenario gets a fresh test instance via context.test = self.testcase_class()
  • Instance is deleted after each scenario (del context.test)
  • Even if the empty list isn't set (due to falsy check), the next scenario starts with a clean instance that has no fixtures

Django >= 5.2: Uses class attributes (context.test.__class__.fixtures)

  • Class attributes persist across all test instances
  • When context.fixtures = [] is falsy and skipped, the old fixtures value remains on the class
  • Next scenario's test instance inherits the stale fixtures from the class

This is why the bug manifests on Django 5.2+ but not on older versions - class attributes have different lifetime semantics than instance attributes.

@bittner bittner force-pushed the copilot/fix-fixtures-reset branch from 3f23234 to af792e3 Compare November 15, 2025 13:37
@bittner bittner force-pushed the copilot/fix-fixtures-reset branch from af792e3 to 4112fbf Compare November 15, 2025 13:57
@bittner bittner marked this pull request as ready for review November 15, 2025 13:58
@bittner bittner merged commit 19fea7f into main Nov 15, 2025
21 checks passed
@bittner bittner deleted the copilot/fix-fixtures-reset branch November 15, 2025 14:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fixtures are not resetted between features

2 participants