Here is a dead simple solution:
If you have several test cases which validate the same conditions or the same behaviour for different entry functions of the same object, refactor the duplicate assertion code into a common function.
In the afterwards given example, the member function of the sut which is called has to be a parameter of the refactoredextracted function. I don't know Swift, but I guess it has the necessary functional tools, so take this as pseudocode:
func testMySutFuncionCallsShowLoading( func mySutFunction: ()->() ) { // Given stubService(forAction: actionType) // When mySutFunction() // Then verify(mockDelegate, times(1)).showLoading() } func testHandleReloadDataCallsShowLoading() { testMySutFuncionCallsShowLoading ( ()=>-> sut.handleReloadData()) } func testHandleViewDidLoadCallsShowLoading() { testMySutFuncionCallsShowLoading ( ()=>-> sut.handleViewDidLoad()) } The DRY principle is not only valid for production code, it can (and should) also be applied to testing code.
Note the described problem has not much to do with the fact that loadData is a private function, it stays the same when loadData would become public, or when loadData would be eliminated by copying its code directly into the calling methods.