Skip to content

Commit 696718c

Browse files
committed
refactor(durable funcitons): split into separate helpers
1 parent 7161574 commit 696718c

File tree

6 files changed

+195
-154
lines changed

6 files changed

+195
-154
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using AzureFunctions.TestHelpers.Starters;
6+
using FluentAssertions;
7+
using Microsoft.Azure.WebJobs;
8+
using Microsoft.Azure.WebJobs.Extensions.Timers;
9+
using Microsoft.Extensions.DependencyInjection;
10+
using Microsoft.Extensions.DependencyInjection.Extensions;
11+
using Microsoft.Extensions.Hosting;
12+
using NSubstitute;
13+
using Xunit;
14+
15+
namespace AzureFunctions.TestHelpers.Tests
16+
{
17+
public class DurableFunctionsHelpers : IDisposable
18+
{
19+
private readonly IInjectable _mock;
20+
private readonly IHost _host;
21+
22+
public DurableFunctionsHelpers()
23+
{
24+
_mock = Substitute.For<IInjectable>();
25+
_host = new HostBuilder()
26+
.ConfigureWebJobs(builder => builder
27+
.AddTimers()
28+
.AddDurableTaskInTestHub()
29+
.AddAzureStorageCoreServices()
30+
.UseWebJobsStartup<Startup>()
31+
.ConfigureServices(services => services.Replace(ServiceDescriptor.Singleton(_mock))))
32+
.Build();
33+
34+
_host.StartAsync().ConfigureAwait(false).GetAwaiter().GetResult();
35+
}
36+
37+
[Fact]
38+
public async Task Wait()
39+
{
40+
// Arrange
41+
var jobs = _host.Services.GetService<IJobHost>();
42+
43+
// Act
44+
await jobs.CallAsync(nameof(Starter), new Dictionary<string, object>
45+
{
46+
["timerInfo"] = new TimerInfo(new WeeklySchedule(), new ScheduleStatus())
47+
});
48+
49+
await jobs.Wait()
50+
.ThrowIfFailed()
51+
.Purge();
52+
53+
// Assert
54+
_mock
55+
.Received()
56+
.Execute();
57+
}
58+
59+
[Fact]
60+
public async Task WaitWithTimeout()
61+
{
62+
// Arrange
63+
_mock
64+
.When(x => x.Execute())
65+
.Do(x => Thread.Sleep(60000));
66+
67+
var jobs = _host.Services.GetService<IJobHost>();
68+
69+
// Act
70+
await jobs.CallAsync(nameof(Starter), new Dictionary<string, object>
71+
{
72+
["timerInfo"] = new TimerInfo(new WeeklySchedule(), new ScheduleStatus())
73+
});
74+
75+
jobs.Invoking(async x => await x.Wait(TimeSpan.FromSeconds(20)))
76+
.Should()
77+
.Throw<TaskCanceledException>();
78+
79+
// Assert
80+
_mock.Received()
81+
.Execute();
82+
}
83+
84+
[Fact]
85+
public async Task WaitDoesNotThrow()
86+
{
87+
// Arrange
88+
_mock
89+
.When(x => x.Execute())
90+
.Do(x => throw new InvalidOperationException());
91+
92+
var jobs = _host.Services.GetService<IJobHost>();
93+
94+
// Act
95+
await jobs.CallAsync(nameof(Starter), new Dictionary<string, object>
96+
{
97+
["timerInfo"] = new TimerInfo(new WeeklySchedule(), new ScheduleStatus())
98+
});
99+
100+
await jobs
101+
.Wait()
102+
.Purge();
103+
104+
// Assert
105+
_mock.Received()
106+
.Execute();
107+
}
108+
109+
[Fact]
110+
public async Task ThrowIfFailed()
111+
{
112+
// Arrange
113+
_mock
114+
.When(x => x.Execute())
115+
.Do(x => throw new InvalidOperationException());
116+
117+
var jobs = _host.Services.GetService<IJobHost>();
118+
119+
// Act
120+
await jobs.CallAsync(nameof(Starter), new Dictionary<string, object>
121+
{
122+
["timerInfo"] = new TimerInfo(new WeeklySchedule(), new ScheduleStatus())
123+
});
124+
125+
126+
// Assert
127+
jobs.Invoking(x => x
128+
.Wait(TimeSpan.FromSeconds(20))
129+
.ThrowIfFailed())
130+
.Should()
131+
.Throw<Exception>();
132+
133+
await jobs
134+
.Wait()
135+
.Purge();
136+
}
137+
138+
public void Dispose()
139+
{
140+
_host.Dispose();
141+
}
142+
}
143+
}
Lines changed: 0 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
using System;
21
using System.Collections.Generic;
3-
using System.Threading;
42
using System.Threading.Tasks;
5-
using AzureFunctions.TestHelpers.Starters;
6-
using DurableTask.Core.Exceptions;
73
using FluentAssertions;
84
using Microsoft.Azure.WebJobs;
9-
using Microsoft.Azure.WebJobs.Extensions.Timers;
105
using Microsoft.Extensions.DependencyInjection;
116
using Microsoft.Extensions.DependencyInjection.Extensions;
127
using Microsoft.Extensions.Hosting;
@@ -44,154 +39,5 @@ public static async Task HttpTriggeredFunctionWithDependencyReplacement()
4439
.Execute();
4540
}
4641
}
47-
48-
[Fact]
49-
public static async Task DurableFunction()
50-
{
51-
// Arrange
52-
var mock = Substitute.For<IInjectable>();
53-
using (var host = new HostBuilder()
54-
.ConfigureWebJobs(builder => builder
55-
.AddTimers()
56-
.AddDurableTaskInTestHub()
57-
.AddAzureStorageCoreServices()
58-
.UseWebJobsStartup<Startup>()
59-
.ConfigureServices(services => services.Replace(ServiceDescriptor.Singleton(mock))))
60-
.Build())
61-
{
62-
await host.StartAsync();
63-
var jobs = host.Services.GetService<IJobHost>();
64-
65-
// Act
66-
await jobs.CallAsync(nameof(Starter), new Dictionary<string, object>
67-
{
68-
["timerInfo"] = new TimerInfo(new WeeklySchedule(), new ScheduleStatus())
69-
});
70-
71-
await jobs.Wait()
72-
.ThrowIfFailed()
73-
.Purge();
74-
75-
// Assert
76-
mock
77-
.Received()
78-
.Execute();
79-
}
80-
}
81-
82-
[Fact]
83-
public static async Task WaitWithTimeout()
84-
{
85-
// Arrange
86-
var mock = Substitute.For<IInjectable>();
87-
mock
88-
.When(x => x.Execute())
89-
.Do(x => Thread.Sleep(60000));
90-
91-
using (var host = new HostBuilder()
92-
.ConfigureWebJobs(builder => builder
93-
.AddTimers()
94-
.AddDurableTaskInTestHub()
95-
.AddAzureStorageCoreServices()
96-
.UseWebJobsStartup<Startup>()
97-
.ConfigureServices(services => services.Replace(ServiceDescriptor.Singleton(mock))))
98-
.Build())
99-
{
100-
await host.StartAsync();
101-
var jobs = host.Services.GetService<IJobHost>();
102-
103-
// Act
104-
await jobs.CallAsync(nameof(Starter), new Dictionary<string, object>
105-
{
106-
["timerInfo"] = new TimerInfo(new WeeklySchedule(), new ScheduleStatus())
107-
});
108-
109-
jobs.Invoking(async x => await x.Wait(TimeSpan.FromSeconds(20)))
110-
.Should()
111-
.Throw<TaskCanceledException>();
112-
113-
// Assert
114-
mock.Received()
115-
.Execute();
116-
}
117-
}
118-
119-
[Fact]
120-
public static async Task WaitDoesNotThrow()
121-
{
122-
// Arrange
123-
var mock = Substitute.For<IInjectable>();
124-
mock
125-
.When(x => x.Execute())
126-
.Do(x => throw new InvalidOperationException());
127-
128-
using (var host = new HostBuilder()
129-
.ConfigureWebJobs(builder => builder
130-
.AddTimers()
131-
.AddDurableTaskInTestHub()
132-
.AddAzureStorageCoreServices()
133-
.UseWebJobsStartup<Startup>()
134-
.ConfigureServices(services => services.Replace(ServiceDescriptor.Singleton(mock))))
135-
.Build())
136-
{
137-
await host.StartAsync();
138-
var jobs = host.Services.GetService<IJobHost>();
139-
140-
// Act
141-
await jobs.CallAsync(nameof(Starter), new Dictionary<string, object>
142-
{
143-
["timerInfo"] = new TimerInfo(new WeeklySchedule(), new ScheduleStatus())
144-
});
145-
146-
await jobs
147-
.Wait(TimeSpan.FromSeconds(20))
148-
.Purge();
149-
150-
// Assert
151-
mock.Received()
152-
.Execute();
153-
}
154-
}
155-
156-
[Fact]
157-
public static async Task ThrowIfFailed()
158-
{
159-
// Arrange
160-
var mock = Substitute.For<IInjectable>();
161-
mock
162-
.When(x => x.Execute())
163-
.Do(x => throw new InvalidOperationException());
164-
165-
using (var host = new HostBuilder()
166-
.ConfigureWebJobs(builder => builder
167-
.AddTimers()
168-
.AddDurableTaskInTestHub()
169-
.AddAzureStorageCoreServices()
170-
.UseWebJobsStartup<Startup>()
171-
.ConfigureServices(services => services.Replace(ServiceDescriptor.Singleton(mock))))
172-
.Build())
173-
{
174-
await host.StartAsync();
175-
var jobs = host.Services.GetService<IJobHost>();
176-
177-
// Act
178-
await jobs.CallAsync(nameof(Starter), new Dictionary<string, object>
179-
{
180-
["timerInfo"] = new TimerInfo(new WeeklySchedule(), new ScheduleStatus())
181-
});
182-
183-
184-
// Assert
185-
jobs.Invoking(x => x
186-
.Wait(TimeSpan.FromSeconds(20))
187-
.ThrowIfFailed())
188-
.Should()
189-
.Throw<Exception>();
190-
191-
await jobs
192-
.Wait()
193-
.Purge();
194-
}
195-
}
19642
}
19743
}

AzureFunctions.TestHelpers/AzureFunctions.TestHelpers.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<ItemGroup>
99
<PackageReference Include="Microsoft.AspNetCore.Http" Version="[2.2,3)" />
1010
<PackageReference Include="Microsoft.Azure.WebJobs" Version="[3,4)" />
11+
<PackageReference Include="Microsoft.Azure.WebJobs.Core" Version="3.0.14" />
1112
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.DurableTask" Version="[1.8,2)" />
1213
</ItemGroup>
1314

AzureFunctions.TestHelpers/JobHostExtensions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ namespace AzureFunctions.TestHelpers
77
{
88
public static class JobHostExtensions
99
{
10+
/// <summary>
11+
/// REMARK: This method does NOT throw when orchestrations have failed.
12+
/// Please, chain the <see cref="ThrowIfFailed" /> and <see cref="Purge"/> to this method for that behavior.
13+
/// </summary>
1014
public static async Task<IJobHost> Wait(this IJobHost jobs, TimeSpan? timeout = null)
1115
{
1216
await jobs.CallAsync(nameof(WaitForCompletion),
@@ -15,6 +19,9 @@ await jobs.CallAsync(nameof(WaitForCompletion),
1519
return jobs;
1620
}
1721

22+
/// <summary>
23+
/// Query the status of all orchestrations in current hub and throw an exception if any one failed.
24+
/// </summary>
1825
public static async Task<IJobHost> ThrowIfFailed(this Task<IJobHost> task)
1926
{
2027
var jobs = await task;
@@ -23,6 +30,9 @@ public static async Task<IJobHost> ThrowIfFailed(this Task<IJobHost> task)
2330
return jobs;
2431
}
2532

33+
/// <summary>
34+
/// Purge the history of all (completed, failed and terminated) orchestrations.
35+
/// </summary>
2636
public static async Task<IJobHost> Purge(this Task<IJobHost> task)
2737
{
2838
var jobs = await task;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using DurableTask.Core;
5+
using Microsoft.Azure.WebJobs;
6+
7+
namespace AzureFunctions.TestHelpers
8+
{
9+
public static class PurgeFunction
10+
{
11+
[FunctionName(nameof(PurgeFunction))]
12+
public static async Task Run([OrchestrationClient]DurableOrchestrationClientBase client)
13+
{
14+
await client.PurgeInstanceHistoryAsync(
15+
DateTime.MinValue,
16+
null,
17+
new []{ OrchestrationStatus.Completed, OrchestrationStatus.Terminated, OrchestrationStatus.Failed });
18+
}
19+
}
20+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Microsoft.Azure.WebJobs;
6+
7+
namespace AzureFunctions.TestHelpers
8+
{
9+
public static class ThrowIfFailedFunction
10+
{
11+
[FunctionName(nameof(ThrowIfFailedFunction))]
12+
public static async Task Run([OrchestrationClient]DurableOrchestrationClientBase client)
13+
{
14+
var failed = (await client.GetStatusAsync()).Where(x => x.RuntimeStatus == OrchestrationRuntimeStatus.Failed).ToList();
15+
if (failed.Any())
16+
{
17+
throw new AggregateException(failed.Select(x => new Exception(x.Output.ToString())));
18+
}
19+
}
20+
}
21+
}

0 commit comments

Comments
 (0)