Skip to content

Commit 5d59448

Browse files
author
Christoph Bühler
committed
feat(testing): add possibility to mock and test finalizers.
This relates to #8.
1 parent b5e1a8e commit 5d59448

File tree

11 files changed

+198
-25
lines changed

11 files changed

+198
-25
lines changed

src/KubeOps/Operator/KubernetesOperator.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public class KubernetesOperator
3838
.CreateDefaultBuilder()
3939
.UseConsoleLifetime();
4040

41+
protected IHost? OperatorHost { get; set; }
42+
4143
public KubernetesOperator()
4244
: this((Assembly.GetEntryAssembly()?.GetName().Name ?? DefaultOperatorName).ToLowerInvariant())
4345
{
@@ -82,15 +84,15 @@ public virtual Task<int> Run(string[] args)
8284

8385
ConfigureOperatorLogging(args);
8486

85-
var host = Builder.Build();
87+
OperatorHost = Builder.Build();
8688

8789
app
8890
.Conventions
8991
.UseDefaultConventions()
90-
.UseConstructorInjection(host.Services);
92+
.UseConstructorInjection(OperatorHost.Services);
9193

92-
DependencyInjector.Services = host.Services;
93-
JsonConvert.DefaultSettings = () => host.Services.GetRequiredService<JsonSerializerSettings>();
94+
DependencyInjector.Services = OperatorHost.Services;
95+
JsonConvert.DefaultSettings = () => OperatorHost.Services.GetRequiredService<JsonSerializerSettings>();
9496

9597
return app.ExecuteAsync(args);
9698
}

src/KubeOps/Testing/KubernetesTestOperator.cs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using k8s;
55
using k8s.Models;
66
using KubeOps.Operator;
7-
using KubeOps.Operator.DependencyInjection;
7+
using KubeOps.Operator.Client;
88
using KubeOps.Operator.Queue;
99
using Microsoft.Extensions.DependencyInjection;
1010
using Microsoft.Extensions.DependencyInjection.Extensions;
@@ -13,22 +13,22 @@
1313

1414
namespace KubeOps.Testing
1515
{
16-
public class KubernetesTestOperator : KubernetesOperator, IDisposable
16+
public class KubernetesTestOperator : KubernetesOperator
1717
{
18-
public IServiceProvider Services => DependencyInjector.Services;
18+
public IServiceProvider Services { get; private set; } = new ServiceCollection().BuildServiceProvider();
19+
20+
public MockKubernetesClient MockedClient =>
21+
Services.GetRequiredService<IKubernetesClient>() as MockKubernetesClient ??
22+
throw new ArgumentException("Wrong kubernetes client registered.");
1923

2024
public MockResourceEventQueue<TEntity> GetMockedEventQueue<TEntity>()
2125
where TEntity : IKubernetesObject<V1ObjectMeta>
2226
=> Services.GetRequiredService<MockResourceQueueCollection>().Get<TEntity>();
2327

24-
public void Dispose()
25-
{
26-
Services.GetService<IHost>()?.StopAsync();
27-
}
28-
2928
public override Task<int> Run(string[] args)
3029
{
3130
base.Run(args).ConfigureAwait(false);
31+
Services = OperatorHost?.Services ?? throw new ArgumentException("Host not built.");
3232
return Task.FromResult(0);
3333
}
3434

@@ -40,6 +40,9 @@ protected override void ConfigureOperatorServices()
4040
services.RemoveAll(typeof(IResourceEventQueue<>));
4141
services.AddTransient(typeof(IResourceEventQueue<>), typeof(MockResourceEventQueue<>));
4242
services.AddSingleton<MockResourceQueueCollection>();
43+
44+
services.RemoveAll(typeof(IKubernetesClient));
45+
services.AddSingleton<IKubernetesClient, MockKubernetesClient>();
4346
});
4447
base.ConfigureOperatorServices();
4548
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using k8s;
7+
using k8s.Models;
8+
using KubeOps.Operator.Client;
9+
using KubeOps.Operator.Client.LabelSelectors;
10+
11+
namespace KubeOps.Testing
12+
{
13+
public class MockKubernetesClient : IKubernetesClient
14+
{
15+
public IKubernetes ApiClient { get; } = new Kubernetes(KubernetesClientConfiguration.BuildDefaultConfig());
16+
17+
public object? GetResult { get; set; }
18+
19+
public IList<object>? ListResult { get; set; }
20+
21+
public object? SaveResult { get; set; }
22+
23+
public object? CreateResult { get; set; }
24+
25+
public object? UpdateResult { get; set; }
26+
27+
public Task<TResource?> Get<TResource>(string name, string? @namespace = null)
28+
where TResource : class, IKubernetesObject<V1ObjectMeta>
29+
=> Task.FromResult(GetResult as TResource);
30+
31+
public Task<IList<TResource>> List<TResource>(string? @namespace = null, string? labelSelector = null)
32+
where TResource : IKubernetesObject<V1ObjectMeta>
33+
=> Task.FromResult(ListResult as IList<TResource> ?? new List<TResource>());
34+
35+
public Task<IList<TResource>> List<TResource>(string? @namespace = null, params ILabelSelector[] labelSelectors)
36+
where TResource : IKubernetesObject<V1ObjectMeta>
37+
=> Task.FromResult(ListResult as IList<TResource> ?? new List<TResource>());
38+
39+
public Task<TResource> Save<TResource>(TResource resource)
40+
where TResource : class, IKubernetesObject<V1ObjectMeta>
41+
=> Task.FromResult(SaveResult as TResource)!;
42+
43+
public Task<TResource> Create<TResource>(TResource resource)
44+
where TResource : IKubernetesObject<V1ObjectMeta>
45+
=> Task.FromResult((TResource) CreateResult!)!;
46+
47+
public Task<TResource> Update<TResource>(TResource resource)
48+
where TResource : IKubernetesObject<V1ObjectMeta>
49+
=> Task.FromResult((TResource) UpdateResult!)!;
50+
51+
public Task UpdateStatus<TStatus>(IStatus<TStatus> resource)
52+
=> Task.CompletedTask;
53+
54+
public Task Delete<TResource>(TResource resource)
55+
where TResource : IKubernetesObject<V1ObjectMeta>
56+
=> Task.CompletedTask;
57+
58+
public Task Delete<TResource>(IEnumerable<TResource> resources)
59+
where TResource : IKubernetesObject<V1ObjectMeta>
60+
=> Task.CompletedTask;
61+
62+
public Task Delete<TResource>(params TResource[] resources)
63+
where TResource : IKubernetesObject<V1ObjectMeta>
64+
=> Task.CompletedTask;
65+
66+
public Task Delete<TResource>(string name, string? @namespace = null)
67+
where TResource : IKubernetesObject<V1ObjectMeta>
68+
=> Task.CompletedTask;
69+
70+
public Task<Watcher<TResource>> Watch<TResource>(
71+
TimeSpan timeout,
72+
Action<WatchEventType, TResource> onEvent,
73+
Action<Exception>? onError = null,
74+
Action? onClose = null,
75+
string? @namespace = null,
76+
CancellationToken cancellationToken = default)
77+
where TResource : IKubernetesObject<V1ObjectMeta>
78+
=> Task.FromResult(
79+
new Watcher<TResource>(
80+
() => Task.FromResult(new StreamReader(new MemoryStream())),
81+
(_, __) => { },
82+
_ => { }));
83+
}
84+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
using Xunit;
2+
3+
[assembly: CollectionBehavior(
4+
CollectionBehavior.CollectionPerAssembly,
5+
MaxParallelThreads = 1,
6+
DisableTestParallelization = true)]

tests/KubeOps.TestOperator.Test/Controller/TestController.Test.cs renamed to tests/KubeOps.TestOperator.Test/TestController.Test.cs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System;
2-
using System.Threading;
3-
using System.Threading.Tasks;
1+
using System.Threading.Tasks;
42
using KubeOps.Testing;
53
using KubeOps.TestOperator.Entities;
64
using KubeOps.TestOperator.TestManager;
@@ -9,9 +7,9 @@
97
using Moq;
108
using Xunit;
119

12-
namespace KubeOps.TestOperator.Test.Controller
10+
namespace KubeOps.TestOperator.Test
1311
{
14-
public class TestControllerTest : IDisposable
12+
public class TestControllerTest
1513
{
1614
private readonly Mock<IManager> _mock = new Mock<IManager>();
1715

@@ -35,6 +33,7 @@ public async Task Test_If_Manager_Created_Is_Called()
3533
await _operator.Run();
3634
_mock.Setup(o => o.Created(It.IsAny<TestEntity>()));
3735
_mock.Verify(o => o.Created(It.IsAny<TestEntity>()), Times.Never);
36+
_operator.MockedClient.UpdateResult = new TestEntity();
3837
var queue = _operator.GetMockedEventQueue<TestEntity>();
3938
queue.Created(new TestEntity());
4039
_mock.Verify(o => o.Created(It.IsAny<TestEntity>()), Times.Once);
@@ -83,10 +82,5 @@ public async Task Test_If_Manager_StatusModified_Is_Called()
8382
queue.StatusUpdated(new TestEntity());
8483
_mock.Verify(o => o.StatusModified(It.IsAny<TestEntity>()), Times.Once);
8584
}
86-
87-
public void Dispose()
88-
{
89-
_operator.Dispose();
90-
}
9185
}
9286
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System.Threading.Tasks;
2+
using k8s.Models;
3+
using KubeOps.Testing;
4+
using KubeOps.TestOperator.Entities;
5+
using KubeOps.TestOperator.Finalizer;
6+
using KubeOps.TestOperator.TestManager;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Extensions.DependencyInjection.Extensions;
9+
using Moq;
10+
using Xunit;
11+
12+
namespace KubeOps.TestOperator.Test
13+
{
14+
public class TestFinalizerTest
15+
{
16+
private readonly Mock<IManager> _mock = new Mock<IManager>();
17+
18+
private readonly KubernetesTestOperator _operator;
19+
20+
public TestFinalizerTest()
21+
{
22+
_operator = new Operator()
23+
.ConfigureServices(
24+
services =>
25+
{
26+
services.RemoveAll(typeof(IManager));
27+
services.AddSingleton(typeof(IManager), _mock.Object);
28+
})
29+
.ToKubernetesTestOperator();
30+
}
31+
32+
[Fact]
33+
public async Task Test_If_Manager_Finalized_Is_Called()
34+
{
35+
await _operator.Run();
36+
_mock.Setup(o => o.Finalized(It.IsAny<TestEntity>()));
37+
_mock.Verify(o => o.Finalized(It.IsAny<TestEntity>()), Times.Never);
38+
_operator.MockedClient.UpdateResult = new TestEntity();
39+
var queue = _operator.GetMockedEventQueue<TestEntity>();
40+
queue.Finalizing(
41+
new TestEntity
42+
{
43+
Metadata = new V1ObjectMeta
44+
{
45+
Finalizers = new[] { new TestEntityFinalizer(_mock.Object).Identifier },
46+
}
47+
});
48+
_mock.Verify(o => o.Finalized(It.IsAny<TestEntity>()), Times.Once);
49+
}
50+
}
51+
}

tests/KubeOps.TestOperator/Controller/TestController.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using System;
22
using System.Threading.Tasks;
33
using KubeOps.Operator.Controller;
4+
using KubeOps.Operator.Entities.Extensions;
45
using KubeOps.Operator.Rbac;
56
using KubeOps.TestOperator.Entities;
7+
using KubeOps.TestOperator.Finalizer;
68
using KubeOps.TestOperator.TestManager;
79

810
namespace KubeOps.TestOperator.Controller
@@ -20,6 +22,7 @@ public TestController(IManager manager)
2022
protected override async Task<TimeSpan?> Created(TestEntity resource)
2123
{
2224
_manager.Created(resource);
25+
await resource.RegisterFinalizer<TestEntityFinalizer, TestEntity>();
2326
return await base.Created(resource);
2427
}
2528

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.Threading.Tasks;
2+
using KubeOps.Operator.Finalizer;
3+
using KubeOps.TestOperator.Entities;
4+
using KubeOps.TestOperator.TestManager;
5+
6+
namespace KubeOps.TestOperator.Finalizer
7+
{
8+
public class TestEntityFinalizer : ResourceFinalizerBase<TestEntity>
9+
{
10+
private readonly IManager _manager;
11+
12+
public TestEntityFinalizer(IManager manager)
13+
{
14+
_manager = manager;
15+
}
16+
17+
public override Task Finalize(TestEntity resource)
18+
{
19+
_manager.Finalized(resource);
20+
return Task.CompletedTask;
21+
}
22+
}
23+
}

tests/KubeOps.TestOperator/Operator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using KubeOps.Operator;
22
using KubeOps.TestOperator.Controller;
33
using KubeOps.TestOperator.Entities;
4+
using KubeOps.TestOperator.Finalizer;
45
using KubeOps.TestOperator.TestManager;
56
using Microsoft.Extensions.DependencyInjection;
67

@@ -15,6 +16,7 @@ public Operator()
1516
{
1617
services.AddTransient<IManager, TestManager.TestManager>();
1718
services.AddResourceController<TestController, TestEntity>();
19+
services.AddResourceFinalizer<TestEntityFinalizer, TestEntity>();
1820
});
1921
}
2022
}

tests/KubeOps.TestOperator/TestManager/IManager.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,7 @@ public interface IManager
1313
void NotModified(TestEntity entity);
1414

1515
void Deleted(TestEntity entity);
16+
17+
void Finalized(TestEntity entity);
1618
}
1719
}

0 commit comments

Comments
 (0)