> Should savePeople() be unit tested

Yes, it should. But try to write your test conditions in a way that is independent from the implementation. For example, turning your usage example into a unit test:

    function testSavePeople() {
        myDataStore = new Store('some connection string', 'password');
        myPeople = ['Joe', 'Maggie', 'John'];
        savePeople(myDataStore, myPeople);
        assert(myDataStore.containsPerson('Joe'));
        assert(myDataStore.containsPerson('Maggie'));
        assert(myDataStore.containsPerson('John'));
    }

This test does multiple things:

 - it verifies the contract of the function savePeople()
 - it does not care about the implementation of savePeople()
 - it documents the example usage of savePeople()

Take note that you can still mock out the data store - but don't check for explicit function calls, check for the result of the operation. This makes it so that you are prepared for future changes/refactors.

For example, your data store implementation might provide a saveBulkPerson() method in the future - now a change to the implementation of savePeople() to use saveBulkPerson() would not break the unit test as long as saveBulkPerson() works as expected. And if saveBulkPerson() somehow does not work as expected, your unit test *will* catch that.

> or would such tests amount to testing the built-in forEach language construct?

As said, test for expected results and the function interface, not for the implementation (unless you are doing integration tests - then catching specific function calls might be of use). *If there are multiple ways to implement a function, all of them should work with your unit test.*


Regarding your update of the question:

Test for state changes! E.g. some of the dough will be used. According to your implementation, assert that the amount of used dough fits into a pan or assert that there is no dough left (meaning, dough is now invalid). Assert that the pan contains cookies after the function call. Assert that the oven is empty afterwards.

For additional tests, verify edge cases: What happens if the oven is not empty? What happens if there isn't enough dough? If the pan is already full?

You should be able to deduce all the required data for these tests from the dough, pan and oven objects themselves. No need to capture the function calls themselves. Treat the function as if its implementation would not be available to you! In fact, most TDD users write their tests before they write the function.