Team A is building a component that depends on Team B's component. These components could range from anything such as: an application A built on a file system B, or web service A backed by another web service B; or driver A for database B. These teams have different code bases. (Although there are probably many other teams and dependencies in real world scenarios.
Team A and B get together to define the interface that the systems will share. Depending on how agile the teams are, this interface is either fully specified (after an entire design phase) and set in stone, or loosely specified with stages of iteration towards something that is final.
Development and Unit Testing
In parallel, Team A and B now go about creating their components and the tests for their individual components based on the specifications. The specifications allow for such parallelism between the teams, instead of waiting on each other. These tests include code-level unit tests as well as functional tests.
As part of Team B's functional tests, they will test that interface to component B, to ensure that component B functions correctly to all scenarios of how component A will use that interface for B.
As part of Team A's functional tests, they will create a mock component B for which they can control behaviour or responses in order to verify component A functions correctly to both success and failure conditions of component B. These mocks can be very simple (if methods are tested in isolation), or can be very complex with lots of state and logic in order to properly emulate component B's behaviour if B itself is non-trivial. Good mocking tools and a dedicated test team creating these mocks can help with the engineering effort required.
Team A can create some key test scenarios for calling the interface to component B (not using component A at all). These acceptance tests run before the integration phase to verify that Team B's component is ready for integration. By not using component A, and purely using good test code, Team A can assert that any test failures are the result of bugs in component B. By having these key scenarios tested ahead of integration, time is saved on both teams from investigating test failures that involve both component A and B. If component B's functional tests are comprehensive, these acceptance tests should merely be a subset of tests already written by Team B.
Team A modifies (or create variants) of some of their functional tests of component A to not use mocks and emulated behaviours but the real component B. These same functional tests that passed on the mocks should now pass on the real component. These tests are called integration tests. Depending on engineering resources, integration tests may only be a subset of functional tests (especially the positive/success scenarios), especially if the teams are confident of their functional testing coverage and correctness. If bugs are found during integration testing, investigation is done to figure out which of the components need to be fixed.
Rinse and repeat at all levels of the dependency graph of teams/components.