diff --git a/src/developer/debug/debug_agent/integration_tests/watchpoint_test.cc b/src/developer/debug/debug_agent/integration_tests/watchpoint_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..ccb10a0f5b0fbfea260bc59f67f6e037fafcea96
--- /dev/null
+++ b/src/developer/debug/debug_agent/integration_tests/watchpoint_test.cc
@@ -0,0 +1,233 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <gtest/gtest.h>
+
+#include "src/developer/debug/debug_agent/integration_tests/message_loop_wrapper.h"
+#include "src/developer/debug/debug_agent/integration_tests/mock_stream_backend.h"
+#include "src/developer/debug/debug_agent/integration_tests/so_wrapper.h"
+#include "src/developer/debug/shared/logging/logging.h"
+#include "src/developer/debug/shared/zx_status.h"
+
+namespace debug_agent {
+
+using namespace debug_ipc;
+
+namespace {
+
+constexpr int kInvalidReturnCode = 0xdeadbeef;
+
+// Receives the notification from the DebugAgent.
+// The implementation is at the end of the file.
+class WatchpointStreamBackend : public MockStreamBackend {
+ public:
+  WatchpointStreamBackend(MessageLoop* loop) : loop_(loop) {}
+
+  // API -----------------------------------------------------------------------
+
+  void ResumeAllThreads();
+  void ResumeAllThreadsAndRunLoop();
+
+  // Notification Interception -------------------------------------------------
+
+  void HandleNotifyModules(NotifyModules) override;
+  void HandleNotifyException(NotifyException) override;
+  void HandleNotifyProcessExiting(NotifyProcessExiting) override;
+
+  // Getters -------------------------------------------------------------------
+
+  MessageLoop* loop() const { return loop_; }
+  uint64_t so_test_base_addr() const { return so_test_base_addr_; }
+
+  zx_koid_t process_koid() const { return process_koid_; }
+  zx_koid_t thread_koid() const { return thread_koid_; }
+  int return_code() const { return return_code_; }
+
+  const auto& exceptions() const { return exceptions_; }
+
+ private:
+  // Each trapped notification will forward the decision whether to quit the
+  // loop to this call according to the TestStage enum.
+  void ShouldQuitLoop();
+
+  enum class TestStage {
+    kWaitingForModules,
+    kWaitingForException,
+    kWaitingForExit,
+    kDone,
+  };
+
+  TestStage test_stage_ = TestStage::kWaitingForModules;
+
+  MessageLoop* loop_;
+  uint64_t so_test_base_addr_ = 0;
+
+  zx_koid_t process_koid_ = 0;
+  zx_koid_t thread_koid_ = 0;
+
+  std::vector<NotifyException> exceptions_;
+  int return_code_ = kInvalidReturnCode;
+};
+
+constexpr uint32_t kWatchpointId = 0x1234;
+
+std::pair<LaunchRequest, LaunchReply>
+GetLaunchRequest(const WatchpointStreamBackend& backend, std::string exe);
+
+std::pair<AddOrChangeBreakpointRequest, AddOrChangeBreakpointReply>
+GetWatchpointRequest(const WatchpointStreamBackend& backend, uint64_t address);
+
+TEST(Watchpoint, DefaultCase) {
+  static constexpr const char kTestSo[] = "debug_agent_test_so.so";
+  SoWrapper so_wrapper;
+  ASSERT_TRUE(so_wrapper.Init(kTestSo)) << "Could not load so " << kTestSo;
+
+  uint64_t variable_offset =
+    so_wrapper.GetSymbolOffset(kTestSo, "gWatchpointVariable");
+  ASSERT_NE(variable_offset, 0u);
+
+  MessageLoopWrapper loop_wrapper;
+  {
+    auto* loop = loop_wrapper.loop();
+    WatchpointStreamBackend backend(loop);
+    RemoteAPI* remote_api = backend.remote_api();
+
+    static constexpr const char kExecutable[] =
+        "/pkg/bin/multithreaded_breakpoint_test_exe";
+    auto [lnch_request, lnch_reply] = GetLaunchRequest(backend, kExecutable);
+    remote_api->OnLaunch(lnch_request, &lnch_reply);
+    ASSERT_EQ(lnch_reply.status, ZX_OK) << ZxStatusToString(lnch_reply.status);
+
+    backend.ResumeAllThreadsAndRunLoop();
+
+    // The first thread should've started.
+    ASSERT_NE(backend.process_koid(), 0u);
+    ASSERT_NE(backend.thread_koid(), 0u);
+
+    // We should have the correct module by now.
+    ASSERT_NE(backend.so_test_base_addr(), 0u);
+    uint64_t address = backend.so_test_base_addr() + variable_offset;
+
+    DEBUG_LOG(Test) << std::hex << "Base: 0x" << backend.so_test_base_addr()
+                    << ", Offset: 0x" << variable_offset
+                    << ", Actual Address: 0x" << address;
+
+    auto [wp_request, wp_reply] = GetWatchpointRequest(backend, address);
+    remote_api->OnAddOrChangeBreakpoint(wp_request, &wp_reply);
+    ASSERT_EQ(wp_reply.status, ZX_OK) << ZxStatusToString(wp_reply.status);
+
+    backend.ResumeAllThreadsAndRunLoop();
+
+    // We should've gotten an exception.
+    auto& exceptions = backend.exceptions();
+    ASSERT_EQ(exceptions.size(), 1u);
+
+    auto& exception = exceptions[0];
+    EXPECT_EQ(exception.type, NotifyException::Type::kWatchpoint)
+        << NotifyException::TypeToString(exception.type);
+    EXPECT_EQ(exception.thread.process_koid, backend.process_koid());
+    EXPECT_EQ(exception.thread.thread_koid, backend.thread_koid());
+
+    ASSERT_EQ(exception.hit_breakpoints.size(), 1u);
+    auto& wp = exception.hit_breakpoints[0];
+    EXPECT_EQ(wp.id, kWatchpointId);
+    EXPECT_EQ(wp.hit_count, 1u);
+    EXPECT_EQ(wp.should_delete, true);
+
+    backend.ResumeAllThreadsAndRunLoop();
+
+    // The process should've exited correctly.
+    EXPECT_EQ(backend.return_code(), 0u);
+  }
+
+}
+
+// Helpers ---------------------------------------------------------------------
+
+std::pair<LaunchRequest, LaunchReply>
+GetLaunchRequest(const WatchpointStreamBackend& backend, std::string exe) {
+  LaunchRequest launch_request = {};
+  launch_request.argv = {exe};
+  launch_request.inferior_type = InferiorType::kBinary;
+  return {launch_request, {}};
+}
+
+std::pair<AddOrChangeBreakpointRequest, AddOrChangeBreakpointReply>
+GetWatchpointRequest(const WatchpointStreamBackend& backend, uint64_t address) {
+  // We add a breakpoint in that address.
+  debug_ipc::ProcessWatchpointSetings location = {};
+  location.process_koid = backend.process_koid();
+  location.thread_koid = backend.thread_koid();
+  location.range = {address, address};
+
+  debug_ipc::AddOrChangeBreakpointRequest watchpoint_request = {};
+  watchpoint_request.breakpoint.id = kWatchpointId;
+  watchpoint_request.watchpoint.locations.push_back(location);
+
+  return {watchpoint_request, {}};
+}
+
+// WatchpointStreamBackend Implementation --------------------------------------
+
+// Searches the loaded modules for specific one.
+void WatchpointStreamBackend::HandleNotifyModules(NotifyModules modules) {
+  for (auto& module : modules.modules) {
+    DEBUG_LOG(Test) << "Received module " << module.name;
+    if (module.name == "libdebug_agent_test_so.so") {
+      so_test_base_addr_ = module.base;
+      break;
+    }
+  }
+  ShouldQuitLoop();
+}
+
+// Records the exception given from the debug agent.
+void WatchpointStreamBackend::HandleNotifyException(NotifyException exception) {
+  DEBUG_LOG(Test) << "Received "
+                  << NotifyException::TypeToString(exception.type)
+                  << " on Thread: " << exception.thread.thread_koid;
+  exceptions_.push_back(std::move(exception));
+  ShouldQuitLoop();
+}
+
+void WatchpointStreamBackend::HandleNotifyProcessExiting(
+    NotifyProcessExiting process) {
+  DEBUG_LOG(Test) << "Process " << process.process_koid
+                  << " exiting with return code: " << process.return_code;
+  FXL_DCHECK(process.process_koid == process_koid_);
+  return_code_ = process.return_code;
+  ShouldQuitLoop();
+}
+
+void WatchpointStreamBackend::ShouldQuitLoop() {
+  if (test_stage_ == TestStage::kWaitingForModules) {
+    if (so_test_base_addr_ != 0u) {
+      test_stage_ = TestStage::kWaitingForException;
+      DEBUG_LOG(Test) << "State changed to WAITING FOR EXCEPTION.";
+      loop_->QuitNow();
+      return;
+    }
+  } else if (test_stage_ == TestStage::kWaitingForException) {
+    if (exceptions_.size() == 1u) {
+      test_stage_ = TestStage::kWaitingForExit;
+      DEBUG_LOG(Test) << "State changed to WAITING FOR EXIT.";
+      loop_->QuitNow();
+      return;
+    }
+  } else if (test_stage_ == TestStage::kWaitingForExit) {
+    if (return_code_ != kInvalidReturnCode) {
+      test_stage_ = TestStage::kDone;
+      DEBUG_LOG(Test) << "State changed to DONE.";
+      loop_->QuitNow();
+      return;
+    }
+  }
+
+  FXL_NOTREACHED() << "Invalid test state.";
+}
+
+}  // namespace
+
+}  // namespace debug_agent
+
diff --git a/src/developer/debug/debug_agent/test_data/watchpoint_test_exe.cc b/src/developer/debug/debug_agent/test_data/watchpoint_test_exe.cc
new file mode 100644
index 0000000000000000000000000000000000000000..e25d26514262693355ad1084559dfb63e1aacd02
--- /dev/null
+++ b/src/developer/debug/debug_agent/test_data/watchpoint_test_exe.cc
@@ -0,0 +1,11 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/developer/debug/debug_agent/test_data/test_so_symbols.h"
+
+int main() {
+  // This function will touch a global variable the debugger will set a
+  // watchpoint on.
+  WatchpointFunction();
+}