# 202002 TrendMicro # Code Commits ## Create Budgets ### CreateBudgets.feature Feature: CreateBudgets In order to manage budget of department As a department manager I want to set budget amount of specific year month Scenario: create a budget Given budget for setting | YearMonth | Amount | | 202003 | 31 | When I create Then it should be created successfully And there should be budgets existed | YearMonth | Amount | | 202003 | 31 | ### CreateBudgetsSteps.cs [Binding] public class CreateBudgetsSteps : Steps { private static CreateBudgetsPage _createBudgetsPage; private CreateBudgetResultPage _createBudgetResultPage; [BeforeTestRun] public static void SetUpTestRun() { AtataContext.GlobalConfiguration.UseChrome(). //WithArguments("start-maximized"). WithLocalDriverPath().UseBaseUrl("http://localhost:50564/").UseCulture("en-us") .UseAllNUnitFeatures(); } [BeforeScenario] public static void SetUpScenario() { AtataContext.Configure().Build(); _createBudgetsPage = Go.To<CreateBudgetsPage>(); } [AfterScenario] public static void TearDownScenario() { AtataContext.Current?.CleanUp(); } [Given(@"budget for setting")] public void GivenBudgetForSetting(Table table) { var budget = table.CreateInstance<Budget>(); ScenarioContext.Set(budget); } [When(@"I create")] public void WhenICreate() { var budget = ScenarioContext.Get<Budget>(); _createBudgetResultPage = _createBudgetsPage.Create(budget); } [Then(@"it should be created succeed")] public void ThenItShouldBeCreatedSuccessfully() { _createBudgetResultPage.Status.Should.ContainAll("created", "succeed"); } [Then(@"there should be budgets existed")] public void ThenThereShouldBeBudgetsExisted(Table table) { using (var dbContext = new AccountingEntitiesForTest()) { var budgets = dbContext.Budgets.ToList(); table.CompareToSet(budgets); } } } ### CreateBudgetsPage.cs using _ = CreateBudgetsPage; [Url("budgets/create")] public class CreateBudgetsPage : Page<_> { public CreateBudgetResultPage Create(Budget budget) { return this .YearMonth.Set(budget.YearMonth) .Amount.Set(budget.Amount) .CreateBudget.ClickAndGo(); } [FindByName("Create")] public Button<CreateBudgetResultPage, _> CreateBudget { get; set; } [FindByName("Amount")] public NumberInput<_> Amount { get; set; } [FindByName("YearMonth")] public TextInput<_> YearMonth { get; set; } } ### CreateBudgetResultPage.cs using _ = CreateBudgetResultPage; public class CreateBudgetResultPage : Page<_> { [FindByName("Status")] public Text<_> Status { get; set; } } ### BudgetsController.cs public class BudgetsController : Controller { [HttpGet] public ActionResult Create() { return View(); } [HttpPost] public ActionResult CreateBudgets() { return View(); } } ### Create.cshtml @{ ViewBag.Title = "Create"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>Create</h2> @using (Html.BeginForm("CreateBudgets", "Budgets", FormMethod.Post)) { <div> YearMonth: <input type="text" name="YearMonth"/> </div> <div> Amount: <input type="number" name="Amount"/> </div> <input type="submit" value="Create" name="Create"/> } ### CreateBudgets.cshtml @{ ViewBag.Title = "CreateBudgets"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>CreateBudgets</h2> <p name="Status">budget created succeed!</p> ## Add Unit Test Project for TDD Install nuget packages - NSubstitute - FluentAssertions ### BudgetsControllerTests.cs [TestFixture()] public class BudgetsControllerTests { [Test()] public void create_a_budget_should_invoke_budgetService_save() { var budgetService = Substitute.For<IBudgetService>(); var budgetsController = new BudgetsController(budgetService); budgetsController.CreateBudgets("202003", 31m); budgetService.Received().Save("202003", 31m); } } ### BudgetController.cs CreateBudgets() public class BudgetsController : Controller { private readonly IBudgetService _budgetService; public BudgetsController(IBudgetService budgetService) { _budgetService = budgetService; } [HttpPost] public ActionResult CreateBudgets(string yearMonth, decimal amount) { _budgetService.Save(yearMonth, amount); return View(); } } ### Add a Failed Unit Test: create a budget succeed [Test] public void create_a_budget_succeed() { var viewResult = _budgetsController.CreateBudgets("202003", 31m) as ViewResult; (viewResult.ViewBag.Status as string).Should().ContainAll("created", "succeed"); } ### Add a Constructor of BudgetsController to Initialize BudgetService public class BudgetsController : Controller { private readonly IBudgetService _budgetService; public BudgetsController() { _budgetService = new BudgetService(); } public BudgetsController(IBudgetService budgetService) { _budgetService = budgetService; } } ### BudgetService.cs public class BudgetService : IBudgetService { public void Save(string yearMonth, decimal amount) { Console.WriteLine("todo"); } } ### Add Entities from EF in Web Application ![](https://i.imgur.com/rorjgyO.png) ### BudgetService.Save() using DbContext to Insert a Budget public class BudgetService : IBudgetService { public void Save(string yearMonth, decimal amount) { using (var dbContext = new AccountingEntities()) { dbContext.Budgets.Add(new Budget() { YearMonth = yearMonth, Amount = amount }); dbContext.SaveChanges(); } } } ### Can’t Repeat Acceptance Test Because of Insert Duplicate PKey Budget ![](https://i.imgur.com/R2dhNro.png) ### Clean Budgets Table Data Before Scenario Running [BeforeScenario] public static void SetUpScenario() { AtataContext.Configure().Build(); using (var dbContext = new AccountingEntitiesForTest()) { dbContext.Database.ExecuteSqlCommand("TRUNCATE TABLE [Budgets]"); } _createBudgetsPage = Go.To<CreateBudgetsPage>(); } ## Update the Budget When Budget Existed ### Scenario: update the budget when budget existed Scenario: update the budget when budget existed Given budget for setting | YearMonth | Amount | | 202003 | 310 | And there are budgets | YearMonth | Amount | | 202003 | 31 | When I create Then it should be updated succeed And there should be budgets existed | YearMonth | Amount | | 202003 | 310 | ### New Steps for Initialized Budget Data and Assertion for Updated Status [Given(@"there are budgets")] public void GivenThereAreBudgets(Table table) { using (var dbContext = new AccountingEntitiesForTest()) { var budgets = table.CreateSet<Budget>(); dbContext.Budgets.AddRange(budgets); dbContext.SaveChanges(); } } [Then(@"it should be updated succeed")] public void ThenItShouldBeUpdatedSucceed() { _createBudgetResultPage.Status.Should.ContainAll("updated", "succeed"); } ### Add a Unit Test for BudgetsController: update the budget when budget existed [Test] public void update_the_budget_when_budget_existed() { _budgetService.Save(Arg.Any<string>(), Arg.Any<decimal>()) .ReturnsForAnyArgs(true); var viewResult = _budgetsController.CreateBudgets("202003", 31m) as ViewResult; (viewResult.ViewBag.Status as string).Should().ContainAll("updated", "succeed"); } ### Complete BudgetsController.CreateBudgets() when update the budget [HttpPost] public ActionResult CreateBudgets(string yearMonth, decimal amount) { var isUpdated = _budgetService.Save(yearMonth, amount); ViewBag.Status = isUpdated ? "budget updated succeed!" : "budget created succeed!"; return View(); } ### Complete BudgetService.Save() when update and insert budget public class BudgetService : IBudgetService { public bool Save(string yearMonth, decimal amount) { using (var dbContext = new AccountingEntities()) { var budget = dbContext.Budgets.Find(yearMonth); var isBudgetExisted = budget != null; if (isBudgetExisted) { budget.Amount = amount; } else { dbContext.Budgets.Add(new Budget() { YearMonth = yearMonth, Amount = amount }); } dbContext.SaveChanges(); return isBudgetExisted; } } } ## Query Budgets ### QueryBudgets.Feature Feature: QueryBudgets In order to control budgets As a manager I want to query total amount of a period Scenario: no budgets When I query between "20200401" and "20200401" Then the total amount should be 0.00 ### QueryBudgetSteps ``` [Binding] [Scope(Feature = "QueryBudgets")] public class QueryBudgetsSteps : Steps { private QueryBudgetPage _queryBudgetPage; [BeforeScenario()] public void BeforeScenario() { AtataContext.Configure() .UseChrome() .UseBaseUrl("http://localhost:50564/") .UseCulture("en-us").UseNUnitTestName() .AddNUnitTestContextLogging().LogNUnitError() .UseAssertionExceptionType<NUnit.Framework.AssertionException>() .UseNUnitAggregateAssertionStrategy().Build(); _queryBudgetPage = Go.To<QueryBudgetPage>(); } [AfterScenario()] public void AfterScenario() { AtataContext.Current?.CleanUp(); } [When(@"I query between ""(.*)"" and ""(.*)""")] public void WhenIQueryBetweenAnd(string start, string end) { _queryBudgetPage.Query(start, end); } [Then(@"the total amount should be (.*)")] public void ThenTheTotalAmountShouldBe(string expectedAmount) { _queryBudgetPage.TotalAmount.Should.Equal(expectedAmount); } } ``` ### QueryBudgetPage.cs ``` using _ = QueryBudgetPage; [Url("budgets/query")] public class QueryBudgetPage : Page<_> { public void Query(string start, string end) { this .Start.Set(start) .End.Set(end) .QueryBudget.Click(); } [FindByName("Query")] public Button<_> QueryBudget { get; set; } [FindByName("End")] public TextInput<_> End { get; set; } [FindByName("Start")] public TextInput<_> Start { get; set; } [FindByName("TotalAmount")] public Text<_> TotalAmount { get; set; } } ``` ### BudgetsController.cs ``` public class BudgetsController : Controller { private readonly IBudgetManager _budgetManager; public BudgetsController() { _budgetManager = new BudgetManager(); } public BudgetsController(IBudgetManager budgetManager) { _budgetManager = budgetManager; } // GET: Budgets public ActionResult Create() { return View(); } [HttpPost] public ActionResult CreateBudget(string yearMonth, decimal amount) { var isUpdated = _budgetManager.Save(yearMonth, amount); if (isUpdated) { ViewBag.Status = "budget updated succeed"; } else { ViewBag.Status = "budget created succeed"; } return View(); } [HttpGet] public ActionResult Query() { return View(); } [HttpPost] public ActionResult Query(QueryBudgetViewModel queryBudgetViewModel) { return View(queryBudgetViewModel); } } ``` ### [View] Query.cshtml ``` @model AccountingWeb.ViewModels.QueryBudgetViewModel @{ ViewBag.Title = "Query"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>Query</h2> @using (Html.BeginForm()) { @Html.AntiForgeryToken() <div class="form-horizontal"> <h4>QueryBudgetViewModel</h4> <hr /> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) <div class="form-group"> @Html.LabelFor(model => model.Start, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Start, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Start, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.End, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.End, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.End, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> <div class="col-md-10"> <p name="TotalAmount"> @if (Model != null) { @Model.TotalAmount.ToString("F2") } </p> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Query" name="Query" class="btn btn-default" /> </div> </div> </div> } <div> @Html.ActionLink("Back to List", "Index") </div> ``` ### QueryBudgetViewModel.cs ``` public class QueryBudgetViewModel { public string Start { get; set; } public string End { get; set; } public decimal TotalAmount { get; set; } } ``` ### BudgetsControllerTests.cs ``` [Test] public void query_budgets() { _budgetManager.TotalAmount( new DateTime(2020, 4, 1), new DateTime(2020, 4, 1) ).Returns(91m); var viewResult = _budgetsController.Query(new QueryBudgetViewModel { Start = "20200401", End = "20200401" }) as ViewResult; (viewResult.Model as QueryBudgetViewModel) .TotalAmount.Should().Be(91m); } ``` ### BudgetsController.cs ``` [HttpPost] public ActionResult Query(QueryBudgetViewModel queryBudgetViewModel) { var start = DateTime.ParseExact( queryBudgetViewModel.Start, "yyyyMMdd", null); var end = DateTime.ParseExact( queryBudgetViewModel.End, "yyyyMMdd", null); var totalAmount = _budgetManager.TotalAmount(start, end); queryBudgetViewModel.TotalAmount = totalAmount; return View(queryBudgetViewModel); } ``` ### BudgetManager.cs ``` public decimal TotalAmount(DateTime start, DateTime end) { throw new NotImplementedException(); } ```