Do you really need a stub or do you better use a spy? Or is it a spy object with some stubbed specifications? Let’s see some examples using pyDoubles:

You can choose a stub object to prepare the scenario and then, assert on the returned value of the system under test.

    def test_using_a_stub(self):
        USERID = 20
        collaborator = stub(Collaborator())
        when(collaborator.one_arg_method).with_args(
            USERID).then_return(SUCCESS)    
        sut = SystemUnderTests(collaborator)
    
        result = sut.exercise_method()
        
        assert_that(result, equal_to(OK))

The test above says: “I don’t really care what the SUT does internally as long as the returned value is the expected, but it might need help from a collaborator, so I set it up, just in case.”
Note the “might” part of the sentence. It is not necessary to specify the possible arguments in the call to the collaborator. This tests is more robust and still serves the same:

    def test_stub_simplification(self):
        collaborator = stub(Collaborator())
        when(collaborator.one_arg_method).then_return(SUCCESS)    
        sut = SystemUnderTests(collaborator)
    
        result = sut.exercise_method()
        
        assert_that(result, equal_to(OK))

Now, let’s replace the stub with a spy object, assuming we don’t need to specify any stub behavior:

    def test_if_arguments_are_important_check_them_out(self):
        USERID = 20
        collaborator = spy(Collaborator())
        sut = SystemUnderTests(collaborator)
    
        result = sut.exercise_method()
        
        assert_that_method(collaborator.one_arg_method
               ).was_called().with_args(USERID)
        assert_that(result, equal_to(OK))

The test above means: “The SUT needs to call its collaborator at least once in order to complete the operation. I want to make sure that happens apart from getting the expected value.”
Depending on the problem, we can have just one test with the 2 asserts, or maybe 2 tests, with one assert each.

We didn’t need to define any stub method, but what if we need it?:

    def test_if_arguments_are_important_check_them_out(self):
        USERID = 20
        collaborator = spy(Collaborator())
        when(collaborator.one_arg_method).then_return(SUCCESS)    
        sut = SystemUnderTests(collaborator)
    
        result = sut.exercise_method()
        
        assert_that_method(collaborator.one_arg_method
               ).was_called().with_args(USERID)
        assert_that(result, equal_to(OK))

We don’t tell the stub what arguments is going to receive. That is not important. The stub part is intended to prepare the scenario. The easier the scenario setup is, the better. We do assert on the arguments at the end of the execution.

Conclusion:
If calling the collaborator is a critical part of the action, use a spy and make sure arguments are passed in as they should. If you need to stub out a method in the spy object, do not specify the arguments in the stub definition. In the stub definition, just tell the framework the returned value and later on, assert on the arguments once the system under test execution has finished

Reason: If you specify arguments in the stub definition and also don’t assert at the end, you need to debug the failing test to find out that maybe some argument wasn’t passed in.
It is more efficient to let the doubles framework tell you what was wrong 🙂