public void ShouldWinElectionIfMajorityOfVotesReceived() { var numberOfOtherActors = 9; var actorIds = Enumerable.Range(0, numberOfOtherActors) .Select(_ => Guid.NewGuid()).ToArray(); Func <IEnumerable <Guid> > getOtherActors = () => actorIds; var outbox = new ConcurrentBag <object>(); var eventLog = new ConcurrentBag <object>(); var nodeId = Guid.NewGuid(); _raftNode = new RaftNode(nodeId, outbox.Add, eventLog.Add, getOtherActors); var numberOfSuccessfulVotes = 5; var numberOfFailedVotes = 4; IEnumerable <Response <RequestVote> > CreateVotes(int term, bool result, int numberOfVotes) => Enumerable.Range(0, numberOfVotes).Select(index => new Response <RequestVote>(nodeId, actorIds[index], new RequestVoteResult(term, result, actorIds[index], nodeId))); // Create an election where 5 out of 9 votes are in favor of the // candidate node var newTerm = 1; var successfulVotes = CreateVotes(newTerm, true, numberOfSuccessfulVotes); var failedVotes = CreateVotes(newTerm, false, numberOfFailedVotes); var combinedVotes = successfulVotes .Union(failedVotes).ToArray(); // Start the node and let the election timeout expire // in order to trigger a new election _raftNode.Tell(new Initialize()); Thread.Sleep(200); foreach (var actorId in getOtherActors()) { // Verify the contents of every vote request sent out // by the node bool ShouldContainVoteRequest(object msg) { if (msg is Request <RequestVote> rrv && rrv.RequestMessage is RequestVote requestVote) { return(rrv.RequesterId == actorId && requestVote.CandidateId == nodeId && requestVote.Term == newTerm); } return(false); } Assert.True(outbox.Count(ShouldContainVoteRequest) > 0); } // Send the vote responses back to the node var source = new CancellationTokenSource(); var token = source.Token; var tasks = combinedVotes.Select(vote => _raftNode.TellAsync(new Context(vote, outbox.Add, token))) .ToArray(); // Wait until all vote responses have been sent back to the node Task.WaitAll(tasks); // The node should post an election outcome message Assert.NotEmpty(eventLog); var outcome = eventLog.Where(msg => msg != null && msg is ElectionOutcome) .Cast <ElectionOutcome>() .First(); Assert.Equal(nodeId, outcome.WinningActorId); Assert.Equal(newTerm, outcome.Term); Assert.Subset(actorIds.ToHashSet(), outcome.KnownActors.ToHashSet()); // Verify the votes var quorumCount = actorIds.Length * .51; var matchingVotes = 0; foreach (var vote in combinedVotes) { Assert.IsType <RequestVoteResult>(vote.ResponseMessage); var currentVote = (RequestVoteResult)vote.ResponseMessage; bool HasMatchingVote(RequestVoteResult result) { return(currentVote.VoteGranted == result.VoteGranted && currentVote.CandidateId == result.CandidateId && currentVote.Term == result.Term && currentVote.VoterId == result.VoterId); } matchingVotes += outcome.Votes.Count(HasMatchingVote); } // There should be a majority vote in favor of the candidate Assert.True(matchingVotes >= quorumCount); }