Since artifacts from all previous stages are passed by default, we just need to define stages in correct order. Please try the example below, which could help understanding.
image: ubuntu:18.04 stages: - build_stage - test_stage - deploy_stage build: stage: build_stage script: - echo "building..." >> ./build_result.txt artifacts: paths: - build_result.txt expire_in: 1 week unit_test: stage: test_stage script: - ls - cat build_result.txt - cp build_result.txt unittest_result.txt - echo "unit testing..." >> ./unittest_result.txt artifacts: paths: - unittest_result.txt expire_in: 1 week integration_test: stage: test_stage script: - ls - cat build_result.txt - cp build_result.txt integration_test_result.txt - echo "integration testing..." >> ./integration_test_result.txt artifacts: paths: - integration_test_result.txt expire_in: 1 week deploy: stage: deploy_stage script: - ls - cat build_result.txt - cat unittest_result.txt - cat integration_test_result.txt

And in case to pass artifacts between jobs in different stages, we can use dependencies together with artifacts to pass the artifacts, as described from the document.
And one more simpler example:
image: ubuntu:18.04 build: stage: build script: - echo "building..." >> ./result.txt artifacts: paths: - result.txt expire_in: 1 week unit_test: stage: test script: - ls - cat result.txt - echo "unit testing..." >> ./result.txt artifacts: paths: - result.txt expire_in: 1 week deploy: stage: deploy script: - ls - cat result.txt