Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

examples: Actor UnitTest example #1326

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
10 changes: 10 additions & 0 deletions all.sln
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControllerSample", "examples\AspNetCore\ControllerSample\ControllerSample.csproj", "{3160CC92-1D6E-42CB-AE89-9401C8CEC5CB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Actor", "Actor", "{02374BD0-BF0B-40F8-A04A-C4C4D61D4992}"
ProjectSection(SolutionItems) = preProject
examples\Actor\README.md = examples\Actor\README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IDemoActor", "examples\Actor\IDemoActor\IDemoActor.csproj", "{7957E852-1291-4FAA-9034-FB66CE817FF1}"
EndProject
Expand Down Expand Up @@ -118,12 +121,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.E2E.Test.Actors.Genera
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cryptography", "examples\Client\Cryptography\Cryptography.csproj", "{C74FBA78-13E8-407F-A173-4555AEE41FF3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemoActor.UnitTest", "examples\Actor\DemoActor.UnitTest\DemoActor.UnitTest.csproj", "{67EB5D79-B31D-4CDB-8AA2-BC50AB82828A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{67EB5D79-B31D-4CDB-8AA2-BC50AB82828A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{67EB5D79-B31D-4CDB-8AA2-BC50AB82828A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{67EB5D79-B31D-4CDB-8AA2-BC50AB82828A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{67EB5D79-B31D-4CDB-8AA2-BC50AB82828A}.Release|Any CPU.Build.0 = Release|Any CPU
{C2DB4B64-B7C3-4FED-8753-C040F677C69A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C2DB4B64-B7C3-4FED-8753-C040F677C69A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C2DB4B64-B7C3-4FED-8753-C040F677C69A}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -295,6 +304,7 @@ Global
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{67EB5D79-B31D-4CDB-8AA2-BC50AB82828A} = {02374BD0-BF0B-40F8-A04A-C4C4D61D4992}
{C2DB4B64-B7C3-4FED-8753-C040F677C69A} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{41BF4392-54BD-4FE7-A3EB-CD045F88CA9A} = {DD020B34-460F-455F-8D17-CF4A949F100B}
{B9C12532-0969-4DAC-A2F8-CA9208D7A901} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
Expand Down
14 changes: 9 additions & 5 deletions examples/Actor/ActorClient/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,14 @@ public class Program
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static async Task Main(string[] args)
{
var data = new MyData()
var data = new MyDataWithTTL()
{
PropertyA = "ValueA",
PropertyB = "ValueB",
MyData = new MyData
{
PropertyA = "ValueA",
PropertyB = "ValueB",
},
TTL = TimeSpan.FromMinutes(10),
};

// Create an actor Id.
Expand All @@ -46,7 +50,7 @@ public static async Task Main(string[] args)
var proxy = ActorProxy.Create<IDemoActor>(actorId, "DemoActor");

Console.WriteLine("Making call using actor proxy to save data.");
await proxy.SaveData(data, TimeSpan.FromMinutes(10));
await proxy.SaveData(data);
Console.WriteLine("Making call using actor proxy to get data.");
var receivedData = await proxy.GetData();
Console.WriteLine($"Received data is {receivedData}.");
Expand Down Expand Up @@ -103,7 +107,7 @@ public static async Task Main(string[] args)
await proxy.UnregisterTimer();
Console.WriteLine("Deregistering reminder. Reminders are durable and would not stop until an explicit deregistration or the actor is deleted.");
await proxy.UnregisterReminder();

Console.WriteLine("Registering reminder with repetitions - The reminder will repeat 3 times.");
await proxy.RegisterReminderWithRepetitions(3);
Console.WriteLine("Waiting so the reminder can be triggered");
Expand Down
36 changes: 36 additions & 0 deletions examples/Actor/DemoActor.UnitTest/DemoActor.UnitTest.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6;net7;net8</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.msbuild" Version="2.9.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="GitHubActionsTestLogger" Version="1.1.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\DemoActor\DemoActor.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

</Project>
91 changes: 91 additions & 0 deletions examples/Actor/DemoActor.UnitTest/DemoActorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using Dapr.Actors.Runtime;
using DaprDemoActor;
using IDemoActorInterface;
using Moq;

namespace DemoActor.UnitTest
{
public class DemoActorTests
{
[Fact]
public async Task SaveData_CorrectlyPersistDataWithGiveTTL()
{
// arrange
// Name of the state to be saved in the actor
var actorStateName = "my_data";
// Create a mock actor state manager to simulate the actor state
var mockStateManager = new Mock<IActorStateManager>(MockBehavior.Strict);
// Prepare other dependencies
var bankService = new BankService();
// Create an actor host for testing
var host = ActorHost.CreateForTest<DaprDemoActor.DemoActor>();
// Create an actor instance with the mock state manager and its dependencies
var storageActor = new DaprDemoActor.DemoActor(host, bankService, mockStateManager.Object);
// Prepare test data to be saved
var data = new MyDataWithTTL
{
MyData = new MyData
{
PropertyA = "PropA",
PropertyB = "PropB",
},
TTL = TimeSpan.FromSeconds(10)
};
// Setup the mock state manager to enable the actor to save the state with the SetStateAsync method, and return
// a completed task when the state is saved, so that the actor can continue with the test.
// When MockBehavior.Strict is used, the test will fail if the actor does not call SetStateAsync or
// calls other methods on the state manager.
mockStateManager
.Setup(x => x.SetStateAsync(It.IsAny<string>(), It.IsAny<MyData>(), It.IsAny<TimeSpan>(), It.IsAny<CancellationToken>()))
.Returns(Task.CompletedTask);

// act
await storageActor.SaveData(data);

// assert
// Verify that the state manager is called with the correct state name and data, only one time.
mockStateManager.Verify(x => x.SetStateAsync(
actorStateName,
It.Is<MyData>(x => x.PropertyA == "PropA" && x.PropertyB == "PropB"),
It.Is<TimeSpan>(x => x.TotalSeconds == 10),
It.IsAny<CancellationToken>()),
Times.Once);
}

[Fact]
public async Task GetData_CorrectlyRetrieveData()
{
// arrange
// Name of the state to be saved in the actor
var actorStateName = "my_data";
// Create a mock actor state manager to simulate the actor state
var mockStateManager = new Mock<IActorStateManager>(MockBehavior.Strict);
// Prepare other dependencies
var bankService = new BankService();
// Create an actor host for testing
var host = ActorHost.CreateForTest<DaprDemoActor.DemoActor>();
// Create an actor instance with the mock state manager and its dependencies
var storageActor = new DaprDemoActor.DemoActor(host, bankService, mockStateManager.Object);
// Prepare prepare the state to be returned by the state manager
var state = new MyData
{
PropertyA = "PropA",
PropertyB = "PropB",
};
// Setup the mock state manager to return the state when the actor calls GetStateAsync.
mockStateManager
.Setup(x => x.GetStateAsync<MyData>(actorStateName, It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(state));

// act
var result = await storageActor.GetData();

// assert
// Verify that the state manager is called with the correct state name, only one time.
mockStateManager.Verify(x => x.GetStateAsync<MyData>(actorStateName, It.IsAny<CancellationToken>()), Times.Once);
// Verify that the actor returns the correct data.
Assert.Equal("PropA", result.PropertyA);
Assert.Equal("PropB", result.PropertyB);
}
}
}
43 changes: 37 additions & 6 deletions examples/Actor/DemoActor/DemoActor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,58 +33,83 @@ public class DemoActor : Actor, IDemoActor, IBankActor, IRemindable

private readonly BankService bank;

public DemoActor(ActorHost host, BankService bank)
/// <summary>
/// Initializes a new instance of <see cref="DemoActor"/>.
/// </summary>
/// <param name="host">ActorHost.</param>
/// <param name="bank">BankService.</param>
/// <param name="actorStateManager">ActorStateManager used in UnitTests.</param>
public DemoActor(
ActorHost host,
BankService bank,
IActorStateManager actorStateManager = null)
: base(host)
{
// BankService is provided by dependency injection.
// See Program.cs
this.bank = bank;

// Assign ActorStateManager when passed as parameter.
// This is used in UnitTests.
if (actorStateManager != null)
{
this.StateManager = actorStateManager;
}
}

public async Task SaveData(MyData data, TimeSpan ttl)
/// <inheritdoc/>
public async Task SaveData(MyDataWithTTL data)
{
Console.WriteLine($"This is Actor id {this.Id} with data {data}.");

// Set State using StateManager, state is saved after the method execution.
await this.StateManager.SetStateAsync<MyData>(StateName, data, ttl);
await this.StateManager.SetStateAsync<MyData>(StateName, data.MyData, data.TTL);
}

/// <inheritdoc/>
public Task<MyData> GetData()
{
// Get state using StateManager.
return this.StateManager.GetStateAsync<MyData>(StateName);
}

/// <inheritdoc/>
public Task TestThrowException()
{
throw new NotImplementedException();
}

/// <inheritdoc/>
public Task TestNoArgumentNoReturnType()
{
return Task.CompletedTask;
}

/// <inheritdoc/>
public async Task RegisterReminder()
{
await this.RegisterReminderAsync("TestReminder", null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
}

/// <inheritdoc/>
public async Task RegisterReminderWithTtl(TimeSpan ttl)
{
await this.RegisterReminderAsync("TestReminder", null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5), ttl);
}


/// <inheritdoc/>
public async Task RegisterReminderWithRepetitions(int repetitions)
{
await this.RegisterReminderAsync("TestReminder", null, TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(1), repetitions);
}


/// <inheritdoc/>
public async Task RegisterReminderWithTtlAndRepetitions(TimeSpan ttl, int repetitions)
{
await this.RegisterReminderAsync("TestReminder", null, TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(1), repetitions, ttl);
}

/// <inheritdoc/>
public async Task<ActorReminderData> GetReminder()
{
var reminder = await this.GetReminderAsync("TestReminder");
Expand All @@ -98,12 +123,14 @@ public async Task<ActorReminderData> GetReminder()
}
: null;
}


/// <inheritdoc/>
public Task UnregisterReminder()
{
return this.UnregisterReminderAsync("TestReminder");
}

/// <inheritdoc/>
public async Task ReceiveReminderAsync(string reminderName, byte[] state, TimeSpan dueTime, TimeSpan period)
{
// This method is invoked when an actor reminder is fired.
Expand Down Expand Up @@ -131,6 +158,7 @@ public Task RegisterTimer()
return this.RegisterTimerAsync("TestTimer", nameof(this.TimerCallback), serializedTimerParams, TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(3));
}

/// <inheritdoc/>
public Task RegisterTimerWithTtl(TimeSpan ttl)
{
var timerParams = new TimerParams
Expand All @@ -143,6 +171,7 @@ public Task RegisterTimerWithTtl(TimeSpan ttl)
return this.RegisterTimerAsync("TestTimer", nameof(this.TimerCallback), serializedTimerParams, TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(3), ttl);
}

/// <inheritdoc/>
public Task UnregisterTimer()
{
return this.UnregisterTimerAsync("TestTimer");
Expand Down Expand Up @@ -179,6 +208,7 @@ public async Task TimerCallback(byte[] data)
Console.WriteLine("Timer parameter2: " + timerParams.StringParam);
}

/// <inheritdoc/>
public async Task<AccountBalance> GetAccountBalance()
{
var starting = new AccountBalance()
Expand All @@ -191,6 +221,7 @@ public async Task<AccountBalance> GetAccountBalance()
return balance;
}

/// <inheritdoc/>
public async Task Withdraw(WithdrawRequest withdraw)
{
var starting = new AccountBalance()
Expand Down
42 changes: 25 additions & 17 deletions examples/Actor/DemoActor/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Copyright 2021 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -11,23 +11,31 @@
// limitations under the License.
// ------------------------------------------------------------------------

namespace DaprDemoActor
using DaprDemoActor;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<BankService>();
builder.Services.AddActors(options =>
{
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
options.Actors.RegisterActor<DemoActor>();
});

public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
var app = builder.Build();

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}

app.UseRouting();
app.MapActorsHandlers();

await app.RunAsync();
Loading
Loading