diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 1cab2bfd5ad22..a6d9a00b9a86e 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -84,6 +84,7 @@ add_executable(envoy-test
   common/upstream/health_checker_impl_test.cc
   common/upstream/host_utility_test.cc
   common/upstream/load_balancer_impl_test.cc
+  common/upstream/load_balancer_simulation_test.cc
   common/upstream/logical_dns_cluster_test.cc
   common/upstream/resource_manager_impl_test.cc
   common/upstream/sds_test.cc
diff --git a/test/common/upstream/load_balancer_simulation_test.cc b/test/common/upstream/load_balancer_simulation_test.cc
new file mode 100644
index 0000000000000..0648312a2ffde
--- /dev/null
+++ b/test/common/upstream/load_balancer_simulation_test.cc
@@ -0,0 +1,133 @@
+#include "common/runtime/runtime_impl.h"
+#include "common/upstream/load_balancer_impl.h"
+#include "common/upstream/upstream_impl.h"
+
+#include "test/mocks/runtime/mocks.h"
+#include "test/mocks/upstream/mocks.h"
+
+using testing::NiceMock;
+using testing::Return;
+
+namespace Upstream {
+
+static HostPtr newTestHost(const Upstream::Cluster& cluster, const std::string& url,
+                           uint32_t weight = 1, const std::string& zone = "") {
+  return HostPtr{new HostImpl(cluster, url, false, weight, zone)};
+}
+
+/**
+ * This test is for simulation only and should not be run as part of unit tests.
+ */
+class DISABLED_SimulationTest : public testing::Test {
+public:
+  DISABLED_SimulationTest() : stats_(ClusterImplBase::generateStats("", stats_store_)) {
+    ON_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50U))
+        .WillByDefault(Return(50U));
+    ON_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100))
+        .WillByDefault(Return(true));
+    ON_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.percent_diff", 3))
+        .WillByDefault(Return(3));
+  }
+
+  /**
+   * Run simulation with given parameters. Generate statistics on per host requests.
+   *
+   * @param originating_cluster total number of hosts in each zone in originating cluster.
+   * @param all_destination_cluster total number of hosts in each zone in upstream cluster.
+   * @param healthy_destination_cluster total number of healthy hosts in each zone in upstream
+   * cluster.
+   */
+  void run(std::vector<uint32_t> originating_cluster, std::vector<uint32_t> all_destination_cluster,
+           std::vector<uint32_t> healthy_destination_cluster) {
+    stats_.upstream_zone_count_.set(all_destination_cluster.size());
+
+    std::unordered_map<std::string, std::vector<HostPtr>> healthy_map =
+        generateHostsPerZone(healthy_destination_cluster);
+    std::unordered_map<std::string, std::vector<HostPtr>> all_map =
+        generateHostsPerZone(all_destination_cluster);
+
+    std::vector<HostPtr> originating_hosts = generateHostList(originating_cluster);
+    cluster_.healthy_hosts_ = generateHostList(healthy_destination_cluster);
+    cluster_.hosts_ = generateHostList(all_destination_cluster);
+
+    std::map<std::string, uint32_t> hits;
+    for (uint32_t i = 0; i < total_number_of_requests; ++i) {
+      HostPtr from_host = selectOriginatingHost(originating_hosts);
+
+      cluster_.local_zone_hosts_ = all_map[from_host->zone()];
+      cluster_.local_zone_healthy_hosts_ = healthy_map[from_host->zone()];
+
+      ConstHostPtr selected = lb_.chooseHost();
+      hits[selected->url()]++;
+    }
+
+    for (const auto& host_hit_num_pair : hits) {
+      std::cout << fmt::format("url:{}, hits:{}", host_hit_num_pair.first, host_hit_num_pair.second)
+                << std::endl;
+    }
+  }
+
+  HostPtr selectOriginatingHost(const std::vector<HostPtr>& hosts) {
+    // Originating cluster should have roughly the same per host request distribution.
+    return hosts[random_.random() % hosts.size()];
+  }
+
+  /**
+   * Generate list of hosts based on number of hosts in the given zone.
+   * @param hosts number of hosts per zone.
+   */
+  std::vector<HostPtr> generateHostList(const std::vector<uint32_t>& hosts) {
+    std::vector<HostPtr> ret;
+    for (size_t i = 0; i < hosts.size(); ++i) {
+      const std::string zone = std::to_string(i);
+      for (uint32_t j = 0; j < hosts[i]; ++j) {
+        const std::string url = fmt::format("tcp://host.{}.{}:80", i, j);
+        ret.push_back(newTestHost(cluster_, url, 1, zone));
+      }
+    }
+
+    return ret;
+  }
+
+  /**
+   * Generate hosts by zone.
+   * @param hosts number of hosts per zone.
+   */
+  std::unordered_map<std::string, std::vector<HostPtr>>
+  generateHostsPerZone(const std::vector<uint32_t>& hosts) {
+    std::unordered_map<std::string, std::vector<HostPtr>> ret;
+    for (size_t i = 0; i < hosts.size(); ++i) {
+      const std::string zone = std::to_string(i);
+      std::vector<HostPtr> zone_hosts;
+
+      for (uint32_t j = 0; j < hosts[i]; ++j) {
+        const std::string url = fmt::format("tcp://host.{}.{}:80", i, j);
+        zone_hosts.push_back(newTestHost(cluster_, url, 1, zone));
+      }
+
+      ret.insert({zone, std::move(zone_hosts)});
+    }
+
+    return ret;
+  };
+
+  const uint32_t total_number_of_requests = 100000;
+
+  NiceMock<MockCluster> cluster_;
+  NiceMock<Runtime::MockLoader> runtime_;
+  Runtime::RandomGeneratorImpl random_;
+  Stats::IsolatedStoreImpl stats_store_;
+  ClusterStats stats_;
+  // TODO: make per originating host load balancer.
+  RandomLoadBalancer lb_{cluster_, stats_, runtime_, random_};
+};
+
+TEST_F(DISABLED_SimulationTest, strictlyEqualDistribution) {
+  run({1U, 1U, 1U}, {3U, 3U, 3U}, {3U, 3U, 3U});
+}
+
+TEST_F(DISABLED_SimulationTest, unequalZoneDistribution) {
+  run({1U, 1U, 1U}, {5U, 5U, 6U}, {5U, 5U, 6U});
+}
+
+} // Upstream
\ No newline at end of file
