1

I'm looking for a way to structure tests from simpler to more complex and at the same time stick to the DRY principle. Here is an example that might clarify things better. I'll use some pseudo code to explain things.

I have an API that has two endpoints /create_user,/set_user_data.

Let's say I start with this:

@pytest.fixture()
def prepare_database():
    # Prepares the database for the test

def test_create_user(prepare_database):
    # Make API call to /create_user
    # Confirm the user was created in a database

Now for testing the /set_user_data it's actually the /create_user which is now the fixture.

@pytest.fixture()
def prepare_database():
    # Prepares the database for the test

@pytest.fixture()
def create_user(prepare_database):
    # Make API call to /create_user
    # Confirm the user was created in a database

def test_set_user_data(create_user):
    # Set user data for the newly created user
    # Check the database

For the example above one of a solution might be to have the same code as both a test and a fixture.

Another approach would be to assume there is no value to separate this into individual tests but just have a single test case for the whole flow. This would mean testing /create_user and /set_user_data in a single test case. This would work for this simplified example, but imagine a flow with call dependent 20 different API calls. It quickly becomes one huge test case.

I'm hoping to get feedback on the pros and cons on the two approaches and perhaps a third way all together?

  • Have you considered checking your controllers, services and repositories individually, rather than bundling everything together? If your SUT is more or less well structured, the code that contains your business logic knows nothing of how your API calls are handled and that data is stored in databases. But checking each of these components individually, you don't have do deal with all this complexity. blog.thecodewhisperer.com/permalink/integrated-tests-are-a-scam – João Farias Jun 26 at 15:57
1

I'd also take into account how much code the "creating new user" part is. In pytest/Python, it really boils down to just one line of code, something like:

response = requests.post(f"{Config.base_url()}/create_user", headers=headers, json=user_body)

So the question is whether or not it's worth it creating a new fixture for only one line of code?

In my opinion, it's not worth it because you'll write more supportive code than the actual code needed.

Then in your potential fixture:

@pytest.fixture()
def create_user(prepare_database):
    # Make API call to /create_user
    # Confirm the user was created in a database

you're also checking/testing here. But testing should be left for test methods only. Moreover, at this point, you'll already have another test that creates a user and checks everything, so having another fixture that does the same and is only used as a starting point in another test method doesn't feel like the best approach.

Regarding the idea of one big test case. I don't think this is a good idea, it's easy to get lost in it, it can become hard to maintain and if some middle part of it fails, then you don't know whether the rest would run fine or if there'll be some errors as well, so you're not getting as much information out of it as you should. I tend to write smaller test cases where I check only one thing per one test case.

Having said that, I'd go for the following design:

@pytest.fixture()
def prepare_database():
    # Prepares the database for the test

def test_create_user(prepare_database):
    # Make API call to /create_user
    # Confirm the user was created in a database

def test_set_user_data(prepare_database):
    # Make API call to /create_user
    # Set user data for the newly created user
    # Check the database

Yes, I'd duplicate code, exactly one line of code in this place: # Make API call to /create_user.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.