Design, Test and Code like it’s heaven on Earth - Part 19

Published on 11/5/2019

So with this part, we arrive at a point where most of the presentation work is complete. We can load the list of blogs to link out on the screen, and we can load any specific blog requested by the user based on the title and are able to display that. Some small issues remain, such as handling locally hosted images and other static content. Those we’ll get to much later. From this point on I’m going to focus on the authoring of a blog post.

A new page and route

Previously we implemented the domain service for creating and publishing a blog. We executed that code through the unit test framework. With this part, we need to build a minimum user interface that will POST through to that domain service. This means that I’ll probably need a form, and because I’m dealing with Blazor I had to look up what Blazor’s version of form posts are.

@page "/admin/blogs/"
@page "/admin/blogs/{title}"
@using helloserve.com.Adaptors
@using helloserve.com.Models
@inject IBlogServiceAdaptor ServiceAdaptor

@if (Model == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <EditForm Model="@Model" OnValidSubmit="Submit">
        <DataAnnotationsValidator />
        <ValidationSummary />
        <p>
            <label for="title">Title:</label>
            <InputText Id="title" bind-Value="@Model.Title" />
        </p>
        <p>
            <label for="isPublished">Is Published:</label>
            <InputCheckbox Id="isPublished" bind-Value="@Model.IsPublished" />
        </p>
        <p>
            <label for="publishDate">Publish Date:</label>
            <InputDate Id="publishDate" bind-Value="@Model.PublishDate" />
        </p>
        <p>
            <label for="content">Content:</label>
        </p>
        <p>
            <InputTextArea Id="content" bind-Value="@Model.Content" Class="textarea-content" />
        </p>
        <p>
            <button type="submit">Submit</button>
        </p>
    </EditForm>
}

@functions {
    [Parameter]
    private string Title { get; set; } = "new title";

    BlogCreate Model;

    protected override async Task OnInitAsync()
    {
        Model = new BlogCreate(); //await ServiceAdaptor.Read(Title);
    }

    async Task Submit()
    {

    }
}

And the accompanying model.

public class BlogCreate
{
    [Required]
    public string Title { get; set; }
    public string Content { get; set; }
    public bool IsPublished { get; set; }
    public DateTime? PublishDate { get; set; }
}

I copied some of it from the pages we made before and combined it with the necessary bits from the article. It’s a pretty basic form. You’ll notice that the model for this page is not BlogView as before, but instead BlogCreate. I use a separate model for creating the blog post as I do for viewing the blog post. Why is this?

SOLID: S for Security

Single responsibility of course, and as we saw last in part 18, this applies to models also. By using a focused model for a single purpose, and naming it appropriately, it becomes easy to understand and contain what effect changes to the model will have. But there is another side-effect of sharing presentation models for different purposes: you run the risk of leaking information unintentionally. Consider the scenario had I changed my BlogView model to include the IsPublished flag.

Whenever a user requests to view a blog post, his browser would receive an instance of BlogView from my server, and that instance will contain a single data point for this flag IsPublished. It’s not displayed in the browser, of course, but it’s still there in the network tab to see. Of course, this example is benign, and the user can’t really do anything with that piece of information about my blog post.

But what if it was a user record? For example, let’s say you have a UserDisplay class which you use to populate a list of friends. This list should really only show a name, a small avatar or picture, and perhaps a status of being online or not. That’s three properties in your class.

public class UserDisplay 
{
   public string DisplayName { get; set; }
   public string PictureUrl { get; set; }
   public bool OnlineStatus { get; set; }
}

So after some time you want to develop a profile page where the current user can go to change their personal settings and other identifiable information. The DisplayName property is one of those things that the user can change. So you decide to just include more properties in this same class.

public class UserDisplay 
{
   public string DisplayName { get; set; }
   public string PictureUrl { get; set; }
   public bool OnlineStatus { get; set; }
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public string EmailAddress { get; set; }
}

In order to seed this new profile page with the relevant data, you include all these new properties in the existing “ReadUsers” query to your database. After some testing, the new profile page is signed off. But what happens when your users now open their friends list page? If they’re clever enough they can see all the information of their friends’ personal information streaming over the internet to their browser, while only three of those properties are used. This is dangerous and can lead to a lot of heartache and trouble for you, your business and your reputation.

By not sharing a model for different purposes, and by naming the different models appropriately (e.g. UserDisplay and UserProfileEdit` for example) you avoid these sorts of situations.

But server-side code is different

Server-side Blazor (and ASP.NET MVC) works slightly differently where only differences in the DOM (or the final rendered view in MVC) is sent to the user’s browser. This means that your sensitive data stays safely on the server. This is great, and a very big part in the allure of using server-side technologies. But this doesn’t alleviate you from the complexities you introduce when sharing models and different purposes. And it also doesn’t help to change your thinking to the required way when you do have to ship data across the internet, like when building a pure WebAPI. The SOLID principles should always apply.

Submitting

With the edit page complete, it’s time hook to up the submit button. In the page I have an empty method called Submit for this purpose. The article’s examples are pretty anemic regarding this method’s implementation and only writes to the console. But it’s enough to make an educated guess that here is where I should call into the IBlogServiceAdaptor instance that is injected into the page. We need to create a method that will accept this new model I’m using.

public interface IBlogServiceAdaptor
{
    ...        
    Task Submit(BlogCreate blog);
}

This method now needs a unit test.

[TestMethod]
public async Task Submit_Verify()
{
    //arrange
    BlogCreate blog = new BlogCreate();

    //act
    await _adaptor.Submit(blog);

    //assert
    _serviceMock.Verify(x => x.Create(It.IsAny<Blog>()));
}

Before this can pass, we need to implement the interface on the actual service adaptor. But we hit a snag. The adaptor accepts this new BlogCreate model, but has to pass an instance of the domain’s Blog model to the service. This is it’s primary purpose - to map between the domain and the presentation and visa versa. We need to build this mapper first.

CreateMap<BlogCreate, Blog>()
    .ForMember(x => x.Key, opt => opt.Ignore());

At this point we don’t know what the final key for the blog post will be. Previously we implemented this transformation of the title to the key as part of the service offering in the domain layer - meaning it is business logic. We don’t want to presume a value for it in the adaptor, so we ignore it in the mapping. It is also worth noting that because we have a unit test that validates all the mappings, that unit test will fail had we not ignored this property in the mapping. But because we have some customer mapping code, we need to test that.

[TestMethod]
public void BlogConfig_CreateBlog()
{
    //arrange
    BlogCreate blog = new BlogCreate() { Title = "title", Content = "content" };

    //act
    Blog result = Config.Mapper.Map<Blog>(blog);

    //assert
    Assert.IsNotNull(result);
    Assert.IsNull(result.Key);
}

And now we’re ready to implement the adaptor’s method.

public async Task Submit(BlogCreate blog)
{
    await _service.Create(Config.Mapper.Map<Domain.Models.Blog>(blog));
}

And then just hook the page up to the service.

async Task Submit()
{
    await ServiceAdaptor.Submit(Model);
}

Concept: Dev Testing

It’s been a while since I introduced a new concept, but here it is. This is something every programmer already does - testing your work manually. This is different from unit testing in a number of ways.

You typically run your code through the debugger when dev testing You have an entire feature completed, top to bottom You have to manually provide input and check the result, just like a user would

This type of testing is still and will always remain important. This type of testing requires a lot of work to be completed first though, including a working user interface and probably also working persistence (e.g. you should have a database running). So, this type of testing occurs very late, and often problems that you uncover here could lead to massive rework had you not prepared and tested your code and architecture through unit testing.

So I dev test browsing to /admin/blogs and enter a title and some content and hit the “Submit” button. I have a breakpoint way down in my database adaptor’s Save method were we did not complete the implementation - it still has the default implementation from code completion. This is fine though, I can inspect the blog entry coming through to be saved using the debugger. It contains a value for the PublishDate. The value is today. But I didn’t fill in the publish date field in my test.

Reviewing the requirements

Now I have to review my implementation and compare it with the requirements. The requirements have a distinction between Create and Publish. The Create requirement makes no mention of setting the date, where-as the Publish requirement does. My implementations for both if these share the same Validate method. The date is set in the validation method when it doesn’t have a value. So my implementation meets the requirements for publishing, but goes the extra mile when simply saving the blog entry.

Note: You can review the requirements in parts 4 (Create) and 5 (Publish).

Change our code by changing your tests

Before we can change any code, we always need to be sure we know how we can test our change. Let’s review the unit tests that we created previously for the Create method. There are four.

[TestMethod]
public async Task Create_Verify()
{
    //arrange
    Blog blog = new Blog() { Title = "Hello Test!" };

    //act
    await Service.Create(blog);

    //assert
    _dbAdaptorMock.Verify(x => x.Save(blog));
    Assert.AreEqual("hello_test", blog.Key);
}

[TestMethod]
public async Task Create_NullDate_IsSet()
{
    //arrange
    Blog blog = new Blog() { Title = "Hello Test!", PublishDate = null };

    //act
    await Service.Create(blog);

    //assert
    Assert.AreEqual(DateTime.Today, blog.PublishDate);
}

[TestMethod]
public async Task Create_HasDate_NotSet()
{
    //arrange
    DateTime expectedPublishDate = DateTime.Today.AddDays(-4);
    Blog blog = new Blog() { Title = "Hello Test!", PublishDate = expectedPublishDate };

    //act
    await Service.Create(blog);

    //assert
    Assert.AreEqual(expectedPublishDate, blog.PublishDate);
}

[TestMethod]
public async Task Create_TitleIsNull_Throws()
{
    //arrange
    Blog blog = new Blog();

    //act/assert
    await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => Service.Create(blog));
}

Here you’ll see two unit tests related to the publish date. One explicitly tests that it is set, and another that it is not set when it already has a value. The first of these two is wrong, and should only apply when we publish. Let’s change that test to instead ensure that it doesn’t get set when it doesn’t have a value.

[TestMethod]
public async Task Create_NullDate_NotSet()
{
    //arrange
    Blog blog = new Blog() { Title = "Hello Test!", PublishDate = null };

    //act
    await Service.Create(blog);

    //assert
    Assert.IsNull(blog.PublishDate);
}

Now we have a failing test, and we can proceed to change our code in order to pass the test. I split the Validate method into two, and consume the shared validation in the publish validation.

private void ValidateSave(Blog blog)
{
    if (string.IsNullOrEmpty(blog.Title))
    {
        throw new ArgumentNullException(nameof(blog.Title));
    }
}

private void ValidatePublish(Blog blog)
{
    ValidateSave(blog);

    blog.PublishDate = blog.PublishDate ?? DateTime.Today;
}

I change the Create and Publish methods to call the appropriate validation methods. Our test now pass, without breaking the other unit tests related to Publish.

Conclusion

In this part we started on implementing the authoring of a blog. I’m able to browse to the administrative section of the site, type up a blog post and save it through a Form POST. The route to final persistence isn’t completed yet, but through using the debugger I was able to confirm that my blog entry is correctly mapped and passed down through my stack in order to save it.

In this part we saw how we can ensure correctness of our change before we make the change, by first changing the unit tests. And we saw how an error in my implementation very long ago in part 4 could quickly and easily be corrected through a combination of unit testing and clean code.

We saw how late in the development lifecycle of the solution dev testing is able to happen. It picked up a major oversight in the implementation compared to the requirements, where unit testing failed to raise this issue - because the unit test aligned with the incorrect implementation. This illustrates how the combination of different methods of testing should be combined to play to each other’s strengths. No one tool, way of working or process is ever enough, and there is no magic bullet.