-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathREADME.md
476 lines (363 loc) · 17.9 KB
/
README.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
# [FastMoq](http://help.fastmoq.com/)
Easy and fast extension of the [Moq](https://github.com/Moq) mocking framework for mocking and auto injection of classes when testing.
## More Documentation
[FAQs](https://github.com/cwinland/FastMoq/wiki/FAQs)
[FastMoq API Documentation](https://cwinland.github.io/FastMoq/Help/html/N-FastMoq.htm)
## Features
- NOW BLAZOR SUPPORT in FastMoq and FastMoq.Web.
- Test without declaring Mocks (unless needed).
- Creates objects with chain of automatic injections in objects and their dependencies.
- Creates Mocks and Objects with properties populated.
- Automatically injects and creates components or services.
- Injection: Automatically determines what interfaces need to be injected into the constructor and creates mocks if they do not exist.
- Generate Mock using specific data.
- Best guess picks the multiple parameter constructor over the default constructor.
- Specific mapping allows the tester to create an instance using a specific constructor and specific data.
- Supports Inject Attributes and multiple constructors.
- Use Mocks without managing fields and properties. Mocks are managed by the Mocker framework. No need to keep track of Mocks. Just use them!
- Create instances of Mocks with non public constructors.
- HttpClient and IFileSystem test helpers
- DbContext support - SqlLite and mockable objects.
- Supports Null method parameter testing.
## Packages
- FastMoq - Combines FastMoq.Core and FastMoq.Web.
- FastMoq.Core - Original FastMoq testing Mocker.
- FastMoq.Web - New Blazor and Web support.
## Targets
- .NET 8
- .NET 6
## Most used classes in the FastMoq namespace
```cs
public class Mocker {} // Primary class for auto mock and injection. This can be used standalone from MockerTestBase using Mocks property on the base class.
public abstract class MockerTestBase<TComponent> where TComponent : class {} // Assists in the creation of objects and provides direct access to Mocker.
```
## Most used classes in the FastMoq.Web.Blazor namespace
```cs
public abstract class MockerBlazorTestBase<T> : TestContext, IMockerBlazorTestHelpers<T> where T : ComponentBase // Assists in the creation of Blazor components and provides direct access to Mocker.
```
## Examples
- [Examples and documentation of MockerTestBase](http://help.fastmoq.com/Help/html/T-FastMoq.MockerTestBase-1.htm)
- [Examples and documentation of MockerBlazorTestBase](http://help.fastmoq.com/Help/html/T-FastMoq.Web.Blazor.MockerBlazorTestBase-1.htm)
### Basic example of the base class creating the Car class and auto mocking ICarService
```cs
public class CarTest : MockerTestBase<Car> {
[Fact]
public void TestCar() {
Component.Color.Should().Be(Color.Green);
Component.CarService.Should().NotBeNull();
}
}
public class Car {
public Color Color { get; set; } = Color.Green;
public ICarService CarService { get; }
public Car(ICarService carService) => CarService = carService;
}
public interface ICarService
{
Color Color { get; set; }
ICarService CarService { get; }
bool StartCar();
}
```
### Example of how to set up for mocks that require specific functionality
```cs
public class CarTest : MockerTestBase<Car> {
public CarTest() : base(mocks => {
mocks.Initialize<ICarService>(mock => mock.Setup(x => x.StartCar).Returns(true));
}
}
```
### Auto Injection
Auto injection allows creation of components with parameterized interfaces. If an override for creating the component is not specified, the component will be created will the default Mock Objects.
#### Auto Injection with instance parameters
Additionally, the creation can be overwritten and provided with instances of the parameters. CreateInstance will automatically match the correct constructor to the parameters given to CreateInstance.
```cs
Mocks.CreateInstance(new MockFileSystem()); // CreateInstance matches the parameters and types with the Component constructor.
```
#### Interface Type Map
When multiple classes derive from the same interface, the Interface Type Map can map with class to use for the given injected interface.
The map can also enable mock substitution.
##### Example of two classes inheriting the same interface
```cs
public class TestClassDouble1 : ITestClassDouble {}
public class TestClassDouble2 : ITestClassDouble {}
```
##### Mapping
This code maps ITestClassDouble to TestClassDouble1 when testing a component with ITestClassDouble.
```cs
Mocker.AddType<ITestClassDouble, TestClassDouble1>();
```
The map also accepts parameters to tell it how to create the instance.
```cs
Mocks.AddType<ITestClassDouble, TestClassDouble1>(() => new TestClassDouble());
```
### DbContext Mocking
Create Your DbContext. *The DbContext can either have virtual DbSet(s) or an interface. In this example, we use virtual DbSets.*
Suppose you have an ApplicationDbContext with a ```virtual DbSet<Blog>```:
```cs
public class ApplicationDbContext : DbContext
{
public virtual DbSet<Blog> Blogs { get; set; }
// ...other DbSet properties
// This can be public, but we make it internal to hide it for testing only.
// This is only required if the public constructor does things that we don't want to do.
internal ApplicationDbContext()
{
// In order for an internal to work, you may need to add InternalsVisibleTo attribute in the AssemblyInfo or project to allow the mocker to see the internals.
// [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
}
// Public constructor used by application.
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) => Database.EnsureCreated();
// ... Initialize and setup
}
```
Suppose you want to test a repository class that uses the ApplicationDbContext:
```cs
public class BlogRepo(ApplicationDbContext dbContext)
{
public Blog? GetBlogById(int id) => dbContext.Blogs.AsEnumerable().FirstOrDefault(x => x.Id == id);
}
```
#### Sample Unit Test Using DbContext in MockerTestBase
In your unit test, you want to override the SetupMocksAction to create a mock DbContext using Mocker.
Here’s an example using xUnit:
```cs
public class BlogRepoTests : MockerTestBase<BlogRepo>
{
// This runs actions before BlogRepo is created.
// This cannot be done in the constructor because the component is created in the base class.
// An alternative way is to pass the code to the base constructor.
protected override Action<Mocker> SetupMocksAction => mocker =>
{
var dbContextMock = mocker.GetMockDbContext<ApplicationDbContext>(); // Create DbContextMock
mocker.AddType(_ => dbContextMock.Object); // Add DbContext to the Type Map.
// In this example, we don't use a IDbContextFactory, but if we did, it would do this instead of AddType:
// mocker.GetMock<IDbContextFactory<ApplicationDbContext>>().Setup(x => x.CreateDbContext()).Returns(dbContextMock.Object);
};
[Fact]
public void GetBlog_ShouldReturnBlog_WhenPassedId()
{
// Arrange
const int ID = 1234;
var blogsTestData = new List<Blog> { new() { Id = ID } };
// Create a mock DbContext
var dbContext = Mocks.GetRequiredObject<ApplicationDbContext>();
dbContext.Blogs.AddRange(blogsTestData); // Can also be dbContext.Set<Blog>().AddRange(blogsTestData)
// Act
var result = Component.GetBlogById(ID);
// Assert
Assert.NotNull(result);
Assert.True(result.Id.Equals(ID));
}
}
```
*Run Your Tests*: Execute your unit tests, and the mock DbContext will behave like a real DbContext, allowing you to test your BlogRepo logic without hitting the actual database.
#### Sample Unit Test Using DbContext Directly In Test Methods
In your unit test, you want to create a mock DbContext using Mocker.
This can be done directly in the test if the context is either only needed for the method or the component is created manually.
Here’s an example using xUnit:
```cs
public class BtnValidatorTests
{
[Fact]
public void BtnValidatorIsValid_ShouldReturnTrue()
{
// Arrange
var id = 1234;
var blogsTestData = new List<Blog> { new Blog { Id = id } };
// Create a mock DbContext
var mocker = new Mocker(); // Create a new mocker only if not already using MockerTestBase; otherwise use Mocks included in the base class.
var dbContextMock = mocker.GetMockDbContext<ApplicationDbContext>();
var dbContext = dbContextMock.Object;
dbContext.Blogs.Add(blogsTestData); // Can also be dbContext.Set<Blog>().Add(blogsTestData)
var validator = new BtnValidator(dbContext);
// Act
var result = validator.IsValid(id);
// Assert
Assert.True(result);
}
}
```
*Run Your Tests*: Execute your unit tests, and the mock DbContext will behave like a real DbContext, allowing you to test your BtnValidator logic without hitting the actual database.
### Null / Parameter checking
Testing constructor parameters is very easy with TestAllConstructorParameters and TestConstructorParameters.
#### Test Methods in MockerTestBase
```TestConstructorParameters``` - Test the constructor that MockerTestBase used to create the Component for the test.
```TestAllConstructorParameters``` - Test all constructors in the component, regardless if the constructor was used to create the component for the test.
#### Test Extension Helper
```EnsureNullCheckThrown``` - Tests the given action, parameter, and constructor parameter to ensure a NullArgumentException is thrown when null.
- ```action``` is the Action to run which must throw the ```ArgumentNullException``` for the specified parameter. This is generally an action provided by ```TestConstructorParameters``` or ```TestAllConstructorParameters```.
- ```parameter``` is the name of the parameter that should throw the ```ArgumentNullException```. The exception must include the name of the parameter which is the default behavior of the ```ArgumentNullException```.
- ```constructor``` (optional) is the constructor being tested. This is the string to display in test output; specifically is multiple constructors are tested.
- ```outputWriter``` (optional) is the ```ITestOutputHelper``` specific to XUnit (```XUnit.Abstractions```)
```cs
action.EnsureNullCheckThrown(parameter, constructor, outputWriter);
```
#### Example: Check all constructor parameters for null and output to the test console
```cs
[Fact]
public void Service_NullArgChecks_AllConstructorsShouldPass() =>
TestAllConstructorParameters((action, constructor, parameter) =>
action.EnsureNullCheckThrown(parameter, constructor, outputWriter));
```
#### Example: Check constructor parameters for null exception
```cs
// Check values for null
[Fact]
public void Service_NullArgChecks() => TestConstructorParameters((action, constructorName, parameterName) =>
{
outputWriter?.WriteLine($"Testing {constructorName}\n - {parameterName}");
action
.Should()
.Throw<ArgumentNullException>()
.WithMessage($"*{parameterName}*");
});
```
#### Example: Check values for specific criteria
```cs
// Check values for specific criteria.
[Fact]
public void Service_NullArgChecks() => TestConstructorParameters((action, constructorName, parameterName) =>
{
outputWriter?.WriteLine($"Testing {constructorName}\n - {parameterName}");
action
.Should()
.Throw<ArgumentNullException>()
.WithMessage($"*{parameterName}*");
},
info =>
{
return info switch
{
{ ParameterType: { Name: "string" }} => string.Empty,
{ ParameterType: { Name: "int" }} => -1,
_ => default,
};
},
info =>
{
return info switch
{
{ ParameterType: { Name: "string" }} => "Valid Value",
{ ParameterType: { Name: "int" }} => 22,
_ => Mocks.GetObject(info.ParameterType),
};
}
);
```
#### Example: Test constructors for null and output to a list while using the extension helper
```cs
// Test constructors for null, using built-in extension and log the output.
[Fact]
public void TestAllConstructors_WithExtension()
{
var messages = new List<string>();
TestAllConstructorParameters((action, constructor, parameter) => action.EnsureNullCheckThrown(parameter, constructor, messages.Add));
messages.Should().Contain(new List<string>()
{
"Testing .ctor(IFileSystem fileSystem, String field)\n - fileSystem",
"Passed fileSystem",
"Testing .ctor(IFileSystem fileSystem, String field)\n - field",
"Passed field",
}
);
}
```
### Call Method with auto injected parameters
When testing method calls on a component, often the method's parameters are mock objects or just need default values. Instead of maintaining the parameter list, methods can be called without specifying specific parameters until required.
FastMoq knows how mocks are already defined and the caller can use those mocks or their own provided mocks, if required.
The helper command, ```CallMethod``` can be used to call any method with or without parameters.
Any method called with ```CallMethod``` can be anywhere such as a component method or a static method. Given the following ```CallTestMethod```, it takes value and reference parameters, many of which can be mocked. If the value cannot be mocked, it can be defaulted.
```cs
public object?[] CallTestMethod(int num, IFileSystem fileSystem, ITestCollectionOrderer dClass, TestClassMultiple mClass, string name)
{
ArgumentNullException.ThrowIfNull(fileSystem);
ArgumentNullException.ThrowIfNull(dClass);
ArgumentNullException.ThrowIfNull(mClass);
ArgumentNullException.ThrowIfNull(num);
ArgumentNullException.ThrowIfNull(name);
return
[
num, fileSystem, dClass, mClass, name,
];
}
```
In this simple call, the method ```CallTestMethod``` will be called with default values and the current mocks.
```cs
[Fact]
public void CallMethod()
{
var result = Mocks.CallMethod<object?[]>(Component.CallTestMethod);
result.Length.Should().Be(5);
result[0].Should().Be(0);
result[1].Should().BeOfType<MockFileSystem>().And.NotBeNull();
result[2].GetType().IsAssignableTo(typeof(ITestCollectionOrderer)).Should().BeTrue();
result[2].Should().NotBeNull();
result[3].GetType().IsAssignableTo(typeof(TestClassMultiple)).Should().BeTrue();
result[3].Should().NotBeNull();
result[4].Should().Be("");
}
```
In the previous call, ```CallMethod``` attempts to use mock parameters and then default values. The value for ```num``` was the ```default(int)``` which is 0. The default string value is ```string.Empty```.
To override a value, parameters can be passed to the method. The parameters do not have to have the same count, but they do require the same order. For example, the following code calls ```CallTestMethod``` with num parameter 4 instead of 0. All other parameters are defaulted to their mocks or value default.
```cs
[Fact]
public void CallMethod_WithParams()
{
var result = Mocks.CallMethod<object?[]>(Component.CallTestMethod, 4);
result.Length.Should().Be(5);
result[0].Should().Be(4);
result[1].Should().BeOfType<MockFileSystem>().And.NotBeNull();
result[2].GetType().IsAssignableTo(typeof(ITestCollectionOrderer)).Should().BeTrue();
result[2].Should().NotBeNull();
result[3].GetType().IsAssignableTo(typeof(TestClassMultiple)).Should().BeTrue();
result[3].Should().NotBeNull();
result[4].Should().Be("");
}
```
In the next call, the first two parameters are overridden only and the other values are the default mock objects/values.
```cs
[Fact]
public void CallMethod_WithParams2()
{
var result = Mocks.CallMethod<object?[]>(Component.CallTestMethod, 4, Mocks.fileSystem);
result.Length.Should().Be(5);
result[0].Should().Be(4);
result[1].Should().BeOfType<MockFileSystem>().And.NotBeNull();
result[2].GetType().IsAssignableTo(typeof(ITestCollectionOrderer)).Should().BeTrue();
result[2].Should().NotBeNull();
result[3].GetType().IsAssignableTo(typeof(TestClassMultiple)).Should().BeTrue();
result[3].Should().NotBeNull();
result[4].Should().Be("");
}
```
Exceptions can be caught just like the method was called directly. The example below shows the assert looking for an argument null exception.
```cs
[Fact]
public void CallMethod_WithException()
{
Assert.Throws<ArgumentNullException>(() => Mocks.CallMethod<object?[]>(Component.CallTestMethod, 4, null));
}
```
## Troubleshooting
### System.MethodAccessException
If the test returns:
- System.MethodAccessException: Attempt by method 'Castle.Proxies.ApplicationDbContextProxy..ctor(Castle.DynamicProxy.IInterceptor[])' to access method
Add the following ```InternalsVisibleTo``` line to the AssemblyInfo file.
```[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]```
## Additional Documentation
[FastMoq API Documentation](https://cwinland.github.io/FastMoq/Help/html/N-FastMoq.htm)
## Full Change Log
[Full Change Log](https://github.com/cwinland/FastMoq/releases)
## Breaking Change
- 2.28 => .NET 7 Removed from support.
- 2.25 => Some methods moved to extensions that are no longer in the MockerTestBase or Mocker. Removed extra CreateInstance<T> methods.
- 2.23.200 => Support .NET 8
- 2.23.x => Removed support for .NET Core 5.
- 2.22.1215 => Removed support for .NET Core 3.1 in FastMoq.Core. Deprecated .NET Core 5 and moved package supporting .NET Core 5.0 from FastMoq to FastMoq.Core.
- 1.22.810 => Removed setters on the MockerTestBase virtual methods: SetupMocksAction, CreateComponentAction, CreatedComponentAction
- 1.22.810 => Update Package Dependencies
- 1.22.728 => Initialize method will reset the mock, if it already exists. This is overridable by settings the reset parameter to false.
- 1.22.604 => Renamed Mocks to Mocker, Renamed TestBase to MockerTestBase.
## [License - MIT](./License)
[http://help.fastmoq.com](http://help.fastmoq.com/)