I'm designing a platform which could be described as a trading platform for buying and selling collectibles, such as trading cards, stamps, coins, etc.
There are several processes which change the state of assets being traded through the platform; for example:
Advertising an asset for sale.
Receiving purchase offers for the asset.
Accepting a purchase offer.
Receiving payment.
For each of these processes, there is a unit of logic which performs each action, and are unit tested using JUnit 5; for example, testing that an asset can be advertised looks like this:
class AdvertiseAssetTests : TestBase() {
private lateinit var asset: Asset
override fun initialize() {
asset = systemUnderTest.advertise(EXAMPLE_TRADING_CARD)
}
#Test
fun `asset should be adverised with the correct status`() {
assertEquals(AssetStatus.ADVERTISED, asset.status)
}
}
In the above class, the final state of the system under test represents advertisement of a single asset against which assertions are made.
The next test would be to assert that offers can be created for the asset; for example:
class CreateAssetOfferTests : TestBase() {
private lateinit var asset: Asset
private lateinit var offer: Offer
override fun initialize() {
asset = systemUnderTest.advertise(EXAMPLE_TRADING_CARD)
offer = systemUnderTest.offer(asset, EXAMPLE_TRADING_CARD_OFFER)
}
#Test
fun `offer should be created for the specified asset`() {
assertEquals(asset.id, offer.assetId)
}
#Test
fun `offer should be created with the correct status`() {
assertEquals(OfferStatus.OFFERED, offer.status)
}
}
...and so on until the final test, which would be to test that the payment for an asset has been received; for example:
class ReceiveAssetPaymentTests : TestBase() {
private lateinit var asset: Asset
private lateinit var offer: Offer
override fun initialize() {
val advertisedAsset = systemUnderTest.advertise(EXAMPLE_TRADING_CARD)
val initialOffer = systemUnderTest.offer(advertisedAsset, EXAMPLE_TRADING_CARD_OFFER)
offer = systemUnderTest.acceptOffer(initialOffer)
asset = systemUnderTest.receivePayment(advertisedAsset)
}
#Test
fun `asset status should be payment received`() {
assertEquals(AssetStatus.PAYMENT_RECEIVED, asset.id)
}
}
As you can see, the further down the process that needs testing, the more test code needs to be added; for example, testing that a payment has been received requires the test setup to advertise, offer, accept an offer and receive payment before any assertions can be made.
This seems like a lot of duplication, since the former processes have already been tested. The problem is that the setup of each test takes longer and longer; for example:
Asset advertisement: 30sec
Offer creation: 30sec (advertisement) + 25sec (offer)
Offer acceptance: 30sec (advertisement) + 25sec (offer) + 25sec (acceptance)
Payment received: 30sec (advertisement) + 25sec (offer) + 25sec (acceptance) + 10sec (payment received)
Is there a way to organise the tests such that the state of the system can allow test classes to execute in order; for example, run AdvertiseAssetTests and then run CreateAssetOfferTests and finally run ReceiveAssetPaymentTests and maintain the state of the system after each test class run?
Is this possible?
Related
UserController:
class UserController(private val graphRepository: GraphRepository) : Controller {
override fun installRoutes(router: Router) {
router.install {
post("/api/v1/user").handler(this#UserController::addUser)
}
}
}
Testing route and calling route handler "addUser":
#Test
fun newUserAdded() {
Mockito.`when`(mockRoutingContext.queryParam("id")).thenReturn(listOf("1"))
Mockito.`when`(mockGraphRepository.getUser("1")).thenReturn(Promise.ofSuccess(null))
Mockito.`when`(mockGraphRepository.enrollUser(any())).thenReturn(Promise.ofSuccess(Unit))
Mockito.`when`(mockRoutingContext.response()).thenReturn(mockHttpServerResponse)
Mockito.doNothing().`when`(mockHttpServerResponse).end()
UserController(mockGraphRepository).addUser(mockRoutingContext)
Mockito.verify(mockRoutingContext, Mockito.times(1)).response()
Mockito.verify(mockHttpServerResponse).end()
}
The main question is how to test the controller route without explicitly calling "addUser" on "UserController" because I want to make the controller function private.
Mocking behavior for types you don't own is generally discouraged for a variety of reasons, such as (but not limited to):
If the real implementation of the mocked dependency changes, the mock's behavior will not automatically reveal any forward-breaking changes.
The more mocks a test introduces, the more cognitive load the test carries, and some tests require a lot of mocks in order to work.
The approach that works best for me is to think of these more as integration tests, and avoid the mocks all together.
To achieve this, I've got an abstract VertxPlatform class that I extend that contains references to resources I commonly refer to across a variety of tests:
the Vertx instance itself
a Router
an EventBus
an HttpServer
a WebClient
These resources is reinitialized per invocation of each test, and the Router is subsequently associated with the HttpServer.
A typical test ends up looking something like this:
class MyHandlerIT : VertxPlatform() {
private lateinit var myHandler: MyHandler // <-- the component under test
#Before override fun setUp(context: TestContext) {
super.setUp(context) // <-- reinitializes all the underlying Vert.x components
myHandler = MyHandler()
router.post("/my/handler/path")
.handler(myHandler.validationHandler())
.handler(myHandler.requestHandler(vertx))
.failureHandler(myHandler.failureHandler())
}
#After override fun tearDown(context: TestContext) {
super.tearDown(context)
}
#Test fun status_400_on_some_condition(context: TestContext) {
val async = context.async()
testRequest(POST, path = "/my/handler/path", params = null, body = null, headers = null)
.subscribeBy(
onSuccess = { response ->
context.assertEquals(BAD_REQUEST.code(), response.statusCode())
async.complete()
},
onError = { error ->
context.fail(error)
}
)
}
}
In each individual test you might have some more case-specific setup. For example, if MyHandler gets results from your GraphRepository via the EventBus you could setup a fake Consumer within the scope of that test that replies with a pre-canned result that server back the values you were otherwise trying to mock.
Hope this helps, or at least inspires some thought!
I have a controller with two parameters and need to test them via unit tests. Want to test 4 parameters, ViewBug, etc. But how I can make fake DB context and logger? I'm stuck at this moment:
[Fact]
public void IndexReturnsAViewResultWithAListOfUsers()
{
// Arrange
var mock = new Mock<AircraftsController>();
var controller = new AircraftsController(/*params*/);
// Act
// Assert
}
This is my controller:
public class AircraftsController : Controller
{
#region DbContext, Logger
public AppDbContext Context { get; }
private readonly ILogger<AircraftsController> _logger;
public AircraftsController(AppDbContext context, ILogger<AircraftsController> logger)
{
Context = context;
_logger = logger;
_logger.LogDebug(1, "NLog injected into Controller");
}
#endregion
[HttpGet]
public IActionResult Compare(int vehicle1, int vehicle2, int vehicle3, int vehicle4)
{
var planesFromDb = Context.Planes.OrderBy(x => x.BR).ToList();
planesFromDb.Insert(0, new Plane { Image = "~/images/EmptyPlane.png", Nation = "EmptyFlag", Name = "Select aircraft", VehicleId=0 });
var selectedPlanes = new List<Plane>();
ViewBag.AllPlanesSelected = planesFromDb;
selectedPlanes.Add(planesFromDb.FirstOrDefault(p => p.VehicleId == vehicle1));
selectedPlanes.Add(planesFromDb.FirstOrDefault(p => p.VehicleId == vehicle2));
selectedPlanes.Add(planesFromDb.FirstOrDefault(p => p.VehicleId == vehicle3));
selectedPlanes.Add(planesFromDb.FirstOrDefault(p => p.VehicleId == vehicle4));
_logger.LogInformation("Log Message");
return View(selectedPlanes);
}
}
As Stephen has suggested the in-memory provider is a good option for mocking an EFCore db context. It works for most things.
I have a requirement to use Moq for 3rd party dependencies so I'd create mocks for both. For the db context I'd use EntityFrameworkCore.Testing (disclaimer, I am the author):
var mockedDbContext = Create.MockedDbContextFor<AppDbContext>();
Then for the logger I'd create a mock using Mock.Of
var mockedLogger = Mock.Of<ILogger<AircraftsController>>();
Easy one liners that you can then use to create your controller in your unit test. Overall I am an advocate of using the EFCore in-memory provider if it suits the unit test. Using mocks does have other advantages such as allowing you to verify invocations.
In .NET Core, you can take advantage of in-memory databases for unit tests. There are two options, EF In-Memory database, and SQLite In-Memory database. I prefer SQLite In-Memory because it gives you all the advantages of handling relational data, unlike EF In-Memory.
Testing with the EF In-Memory Database
SQLite In-Memory Database
Below is a simple implementation for unit tests using SQLite In-Memory database:
public YourContext GetDbContext()
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
var option = new DbContextOptionsBuilder<YourContext>()
.UseSqlite(connection,
s => {
s.UseNetTopologySuite();
s.MigrationsHistoryTable("__MigrationHistory");
}).Options;
var dbContext = new YourContext(option);
//Added to recreate database and run migration for each test.
if (dbContext != null)
{
dbContext.Database.EnsureDeleted();
dbContext.Database.EnsureCreated();
}
return dbContext;
}
And then in unit test:
var context = GetDbContext();
Alternatively, you can place the GetDbContext method in a fixture, so that you are only re-creating the database one time per test class. Have a dispose method in the fixture to run the dbContext.Database.EnsureDeleted() to clean up the data between test classes.
Shared context between Tests - Class Fixtures
I am attempting to write some integration tests against aspnet core 2.2. I want to use a TestStartup class that inherits from the normal Startup class to configure resources and services for testing purposes.
A simple example (can be found here: https://github.com/davidgouge/aspnet-integration-testing):
I have a solution that contains two projects:
IntegrationTestingWeb (a barebones aspnet mvc app)
IntegrationTestingTests (a testing project)
I have a test that uses the Web Startup class and asserts that OK is returned from /Home/Privacy
[Test]
public async Task GetPrivacy_Through_Normal_Startup()
{
var builder = new WebHostBuilder().UseStartup<Startup>();
var client = new TestServer(builder).CreateClient();
var result = await client.GetAsync("/Home/Privacy");
result.StatusCode.Should().Be(HttpStatusCode.OK);
}
This test passes.
If I create a TestStartupInTestProject class that inherites from Startup but place it in the Tests project, then I have to do some extra work when creating the WebHostBuilder but then the test fails.
[Test]
public async Task GetPrivacy_Through_Test_Startup_In_Test_Project()
{
var builder = new WebHostBuilder().ConfigureServices(services =>
{
var startupAssembly = typeof(TestStartupInTestProject).GetTypeInfo().Assembly;
var manager = new ApplicationPartManager();
manager.ApplicationParts.Add(new AssemblyPart(startupAssembly));
manager.ApplicationParts.Add(new AssemblyPart(typeof(HomeController).Assembly));
manager.FeatureProviders.Add(new ControllerFeatureProvider());
manager.FeatureProviders.Add(new ViewComponentFeatureProvider());
services.AddSingleton(manager);
}).UseStartup<TestStartupInTestProject>();
var client = new TestServer(builder).CreateClient();
var result = await client.GetAsync("/Home/Privacy");
result.StatusCode.Should().Be(HttpStatusCode.OK);
}
The error in the failure is:
Tests.Tests.GetPrivacy_Through_Test_Startup_In_Test_Project
System.InvalidOperationException : The view 'Privacy' was not found. The
following locations were searched:
/Views/Home/Privacy.cshtml
/Views/Shared/Privacy.cshtml
/Pages/Shared/Privacy.cshtml
So it looks like because my Startup class is located in the Test project, the views cannot be located. What setting am I missing to be able to find the Views?
It turns out I was missing .UseContentRoot(Directory.GetCurrentDirectory() + "\\..\\..\\..\\..\\IntegrationTestingWeb") when creating the WebHostBuilder. As it sounds, it sets the root dir where the app will look for Views etc.
I have an Autofac DI Container defined as follows:
public class Bootstrapper
{
public IContainer BootStrap()
{
var builder = new ContainerBuilder();
builder.RegisterType<ItemViewModel>().AsSelf();
builder.RegisterType<EventAggregator>()
.As<IEventAggregator>()
.SingleInstance();
}
}
I have a Unit Test defined to test whether a deletion removes the deleted item from the collection:
[Fact]
public void Should_remove_item_from_collection_when_item_is_deleted()
{
const int deletedId = 42;
// adds three items to the collection
_openItemEditViewEvent.Publish(deletedId);
_openItemEditViewEvent.Publish(8);
_openItemEditViewEvent.Publish(9);
// I've tried this:
_eventAggregatorMock.Object.GetEvent<ItemDeletedEvent>().Publish(42);
// and alternatively, this (not at the same time):
_itemDeletedEventMock.Object.Publish(42);
Assert.Equal(2,_vm.ItemEditViewModels.Count); // always fails
Assert.False(_vm.ItemEditViewModels
.Select(vm => vm.Item.Id).Contains(42), "Wrong item deleted");
}
The constructor of the Unit Test initializes and assigns the EventAggregator to the view model:
_eventAggregatorMock = new Mock<IEventAggregator>();
_itemDeletedEventMock = new Mock<ItemDeletedEvent>();
_eventAggregatorMock.Setup(ea => ea.GetEvent<ItemDeletedEvent>())
.Returns(_itemDeletedEventMock.Object);
_vm = new ItemViewModel(_eventAggregatorMock.Object, */ ... /*);
In my actual view model, I Subscribe to the event:
public ItemViewModel(IEventAggregator ea, /* ... */)
{
_eventAggregator.GetEvent<ItemDeletedEvent>()
.Subscribe(OnItemDeleted, true);
}
And we never hit a breakpoint here:
public void OnItemDeleted()
{
// never happens
}
For the life of me, I can't figure out what I'm doing wrong - I'm overlooking something... do I have to Setup the event's Publish event in the Mock? Should I be using a real ItemDeletedEvent instance instead of a Mock? Any help would be greatly appreciated.
=> Hi Scott,
there are 2 ViewModel-scenarios you want to test when using an EventAggregator:
You want to test that your ViewModel is publishing an event
You want to test that your ViewModel does something when an event was published. So the ViewModel has to subscribe to that Event to do something
(Note: The following lines are true for PRISM's EventAggregator, which is the one you're using I guess. For other EventAggregators it could be different)
For the first scenario, you have to create a mock for the event. Then you can verify on that mock-instance that the Publish-method of the Event has been called.
For the second scenario, which is the scenario you have in your question, you have to use the real event in your test. Why?
When you call the Publish-method on a event-mock, that Publish method won't call the subscribers to that Event, as there's no logic behind the Subscribe-method. For sure you could setup both methods and implement that publish/subscribe-logic in your mock. But there's no reason to do so, just use the real Event
When you use the real event, the Publish-method will call all the subscribers. And this is exactly What you need in your test.
It should look like this:
_itemDeletedEvent = new ItemDeletedEvent();
_eventAggregatorMock.Setup(ea => ea.GetEvent<ItemDeletedEvent>())
.Returns(_itemDeletedEvent);
Now your ViewModel will get this itemDeletedEvent-instance from the EventAggregator. In your test you call the Publish-method on this itemDeletedEvent-instance and it will work.
More about this is explained in my Course on Pluralsight about WPF and Test Driven Development: http://www.pluralsight.com/courses/wpf-mvvm-test-driven-development-viewmodels
Thomas
http://www.thomasclaudiushuber.com
I'm trying out TDD on a greenfield hobby app in ASP.NET MVC, and have started to get test methods such as the following:
[Test]
public void Index_GetRequest_ShouldReturnPopulatedIndexViewModel()
{
var controller = new EmployeeController();
controller.EmployeeService = GetPrePopulatedEmployeeService();
var actionResult = (ViewResult)controller.Index();
var employeeIndexViewModel = (EmployeeIndexViewModel)actionResult.ViewData.Model;
EmployeeDetailsViewModel employeeViewModel = employeeIndexViewModel.Items[0];
Assert.AreEqual(1, employeeViewModel.ID);
Assert.AreEqual("Neil Barnwell", employeeViewModel.Name);
Assert.AreEqual("ABC123", employeeViewModel.PayrollNumber);
}
Now I'm aware that ideally tests will only have one Assert.xxx() call, but does that mean I should refactor the above to separate tests with names such as:
Index_GetRequest_ShouldReturnPopulatedIndexViewModelWithCorrectID
Index_GetRequest_ShouldReturnPopulatedIndexViewModelWithCorrectName
Index_GetRequest_ShouldReturnPopulatedIndexViewModelWithCorrectPayrollNumber
...where the majority of the test is duplicated code (which therefore is being tested more than once and violates the "keep tests fast" advice)? That seems to be taking it to the extreme to me, so if I'm right as I am, what is the real-world meaning of the "one assert per test" advice?
It seems just as extreme to me, which is why I a also write multiple asserts per test. I already have >500 tests, writing just one assert per test would blow this up to at least 2500 and my tests would take over 10 minutes to run.
Since a good rest runner (such as Resharper's) lets you see the line where the test failed very quickly, you should still be able to figure out why a test failed with little effort. If you don't mind the extra effort, you can also add an assert description ("asserting payroll number correct"), so that you can even see this without even looking at the source code. With that, there is very little reason left to just one assert per test.
In his book The Art of Unit Testing, Roy Osherove talks about this subject. He too is in favour of testing only one fact in a unit test, but he makes the point that that doesn't always mean only one assertion. In this case, you are testing that given a GetRequest, the Index method ShouldReturnPopulatedIndexViewModel. It seems to me that a populated view model should contain an ID, a Name, and a PayrollNumber so asserting on all of these things in this test is perfectly sensible.
However, if you really want to split out the assertions (say for example if you are testing various aspects which require similar setup but are not logically the same thing), then you could do it like this without too much effort:
[Test]
public void Index_GetRequest_ShouldReturnPopulatedIndexViewModel()
{
var employeeDetailsViewModel = SetupFor_Index_GetRequest();
Assert.AreEqual(1, employeeDetailsViewModel.ID);
}
[Test]
public void Index_GetRequest_ShouldReturnPopulatedIndexViewModel()
{
var employeeDetailsViewModel = SetupFor_Index_GetRequest();
Assert.AreEqual("Neil Barnwell", employeeDetailsViewModel.Name);
}
[Test]
public void Index_GetRequest_ShouldReturnPopulatedIndexViewModel()
{
var employeeDetailsViewModel = SetupFor_Index_GetRequest();
Assert.AreEqual("ABC123", employeeDetailsViewModel.PayrollNumber);
}
private EmployeeDetailsViewModel SetupFor_Index_GetRequest()
{
var controller = new EmployeeController();
controller.EmployeeService = GetPrePopulatedEmployeeService();
var actionResult = (ViewResult)controller.Index();
var employeeIndexViewModel = (EmployeeIndexViewModel)actionResult.ViewData.Model;
var employeeDetailsViewModel = employeeIndexViewModel.Items[0];
return employeeDetailsViewModel;
}
It could also be argued that since these tests require the same setup, they should get their own fixture and have a single [SetUp] method. There's a downside to that approach though. It can lead to lots more unit test classes than actual, real classes which might be undesirable.
I use a helper class to contain the asserts. This keeps the test methods tidy and focused on what they're actually trying to establish. It looks something like:
public static class MvcAssert
{
public static void IsViewResult(ActionResult actionResult)
{
Assert.IsInstanceOfType<ViewResult>(actionResult);
}
public static void IsViewResult<TModel>(ActionResult actionResult, TModel model)
{
Assert.IsInstanceOfType<ViewResult>(actionResult);
Assert.AreSame(model, ((ViewResult) actionResult).ViewData.Model);
}
public static void IsViewResult<TModel>(ActionResult actionResult, Func<TModel, bool> modelValidator)
where TModel : class
{
Assert.IsInstanceOfType<ViewResult>(actionResult);
Assert.IsTrue(modelValidator(((ViewResult) actionResult).ViewData.Model as TModel));
}
public static void IsRedirectToRouteResult(ActionResult actionResult, string action)
{
var redirectToRouteResult = actionResult as RedirectToRouteResult;
Assert.IsNotNull(redirectToRouteResult);
Assert.AreEqual(action, redirectToRouteResult.RouteValues["action"]);
}
}