I know and understand the value of interfaces in Java. You code to the interface, and then you can change your implementations without having to change any code using the interface. Often the term "contract" is used in connection with interfaces. The way I understand it is the interface defines the "contract" between the application and the implementation.
So, when I create an implementation, I have to fulfill the contract. My questions is, what exactly is in that contract that I have to fulfill?
Obviously, at a minimum you have to provide methods with the same signatures as the interface. The code won't compile otherwise. Is that all the "contract" entails? It seems like there should be more.
For example, I've read articles debating the value of testing to the interface vs. testing specific implementations, or doing both. I see great value in having tests for an interface so that you know what inputs have what expected outputs. It would seem to me that this would also be part of the interface "contract". Every implementation of the interface should produce the same outputs from the same inputs. Obviously there's no way to enforce this contract in the code, but it can be enforced through test cases. Am I wrong in my thinking here?
Finally, what about side effects that implementations have? Here, I'm mainly talking about any persistence that might happen as part of the implementation. Say I have an implementation that is saving some records to the DB while it preforms the operation. Would this somehow be part of the interface "contract"? If so, how could you enforce this contract? From the interface level, I have no idea what the implementation is actually doing. All I know is I give it inputs, and it gives me an output, which I can test. Is any persistence that happens also considered an "output"? If so, I just don't see how this can be tested and enforced. I'm a proponent of persistence ignorance, so I could know that something should be persisted, but I don't know how it is persisted. So, I just don't how to tell when something actually persisted. It may be simple if your interface has some simple CRUD operations, but I want to think about more complicated interfaces.
I hope my question makes sense and that someone can provide some good feedback. I want to discuss this generally, but I can provide a specific example if it's not clear what I'm talking about.