Struggling with how test a method with several steps

oliwa

I have a MVC web site with a user registration function and I have one layer that I can't wrap my head around how to test. Basically the method does this...

1) Checks the database to see if the user is already registered

2) Maps a view model into an entity framework model

3) Saves the user to the database

4) Send the user a confirmation email

5) Performs a web service post to a 3rd party API

6) Updates the user (created in step #3) with a value returned from the 3rd party

I am struggling with how or should I test this. I have abstracted all the steps into separate services and I have tests for those so really tests for this method would be testing the flow. Is that valid?

In a TDD world, and I'm trying to think that way, should I have a method like this? Or is there a design issue I am not seeing?

I can write the tests and I understand how to mock but when I write the test for step #6 I have mock setups that return data for steps #1, #2 and #5 to ensure the code gets that far and to ensure the object saved in step #6 has the correct state. My test setups get long quickly.

If this is how it's supposed to be then great! But I feel like I am missing my light bulb moment.

My Lightbulb Moment I liked Keith Payne's answer and looking at his interfaces made me see things from a new perspective. I also watched a TDD Play by Play course (http://www.pluralsight.com/courses/play-by-play-wilson-tdd) and that really helped me understand the process. I was thinking about the process from the inside out and not from the outside in.

This is definitely a new way of thinking about software development.

Keith Payne

Difficult test setups are a code-smell and I think you see this already. The answer is more cowbell (abstraction).

This is a common fault in controller methods acting as both controller of the UI and orchestrating the business process. Steps 5 & 6 probably belong together, steps 1, 3 & 4 similarly should be abstracted away to another method. Really the only thing a controller method should do is to receive data from the view, hand it off to an application- or business- layer service, and compile the results into a new view to display back to the user (mapping).

EDIT:

The AccountManager class that you mention in the comment is a good step towards good abstraction. It lives in the same namespace as the rest of the MVC code, which unfortunately makes it easier to crisscross the dependencies. For instance, passing a view model to the AccountManager is a dependency in the "wrong" direction.

Imagine this idealized architecture for a web application:

Application Layer

  1. UI (JavaScript/HTML/CSS)
  2. Model-View-Controller (Razor/ViewModel/Navigation)
  3. Application Services (Orchestration/Application Logic)

Business Layer

  1. Domain Services (Domain [EF] Models/Unit Of Work/Transactions)
  2. WCF/Third Party API's (Adapters/Client Proxies/Messages)

Data Layer

  1. Database

In this architecture, each item references the item below it.

Inferring some things about your code, AccountManager is at highest an application service (in the referencing hierarchy). I do not think that it is logically part of the MVC or UI components. Now, if those architecture items were in different dlls, the IDE would not allow you to pass a view model into a method of the AccountManager. It would cause a circular dependency.

Outside of the architectural concerns, it is also apparent that the view model is not appropriate to pass along because it will invariably include data that support view rendering that is useless to the AccountManager. It also means that AccountManager must have knowledge of the meaning of properties in the view model. Both the view model class and AccountManager are now dependent on each other. This introduces unnecessary brittleness and fragility to the code.

A better option is to pass along simple parameters, or if you prefer, to package them into a new Data-Transfer-Object (DTO) which would be defined by contract in the same place as the AccountManager.

Some example interfaces:

namespace MyApp.Application.Services
{
    // This component lives in the Application Service layer and is responsible for orchestrating calls into the
    // business layer services and anything else that is specific to the application but not the overall business domain.

    // For instance, sending of a confirmation email is probably a requirement in some application process flows, but not
    // necessarily applicable to every instance of adding a user to the system from every source. Perhaps there is an admin back-end
    // application which may or may not send the email when an administrator registers a new user. So that back-end 
    // application would have a different orchestration component that included a parameter to indicate whether to 
    // send the email, or to send it to more than one recipient, etc.

    interface IAccountManager
    {
        bool RegisterNewUser(string username, string password, string confirmationEmailAddress, ...);
    }
}

namespace MyApp.Domain.Services
{
    // This is the business-layer component for registering a new user. It will orchestrate the
    // mapping to EF models, calling into the database, and calls out to the third-party API.

    // This is the public-facing interface. Implementation of this interface will make calls
    // to a INewUserRegistrator and IExternalNewUserRegistrator components.

    public interface IUserRegistrationService
    {
        NewUserRegistrationResult RegisterNewUser(string username, string password, ...);
    }

    public class NewUserRegistrationResult
    {
        public bool IsUserRegistered { get; set; }
        public int? NewUserId { get; set; }

        // Add additional properties for data that is available after
        // the user is registered. This includes all available relevant information
        // which serves a distinctly different purpose than that of the data returned
        // from the adapter (see below).
    }

    internal interface INewUserRegistrator
    {
        // The implementation of this interface will add the user to the database (or DbContext)
        // Alternatively, this could be a repository 
        User RegisterNewUser(User newUser) ;
    }

    internal interface IExternalNewUserRegistrator
    {
        // Call the adapter for the API and update the user registration (steps 5 & 6)
        // Replace the return type with a class if more detailed information is required

        bool UpdateUserRegistrationFromExternalSystem(User newUser);
    }

    // Note: This is an adapter, the purpose of which is to isolate details of the third-party API
    // from yor application. This means that what comes out from the adapter is determined not by what
    // is provided by the third party API but rather what is needed by the consumer. Oftentimes these
    // are similar.

    // An example of a difference can be some mundance detail. For instance, say that the API
    // returns -1 for some non-nullable int value when the intent is to indicate lack of a match.
    // The adapter would protect the application from that detail by using some logic to interpret
    // the -1 value and set a bool to indicate that no match was found, and to use int?
    // with a null value instead of propagating the magic number (-1) throughout your application.

    internal interface IThirdPartyUserRegistrationAdapter
    {
        // Call the API and interpret the response from the API.
        // Also perform any logging, exception handling, etc.
        AdapterResult RegisterUser(...);
    }

    internal class AdapterResult
    {
        public bool IsSuccessful { get; set; }

        // Additional properties for the response data that is needed by your application only.
        // Do not include data provided by the API response that is not used.
    }
}

Something to keep in mind is that this kind of design-all-at-once is the opposite of TDD. In TDD, the need for these abstractions become apparent as you test and write code from the outside-in. What I've done here is to skip all of that and jump directly to designing the innerworks based on what I picture in my head. In almost all cases this leads to over-design and over-abstraction, which TDD naturally prevents.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

For loops: How could I repeat several steps?

From Dev

How to unwind segue several steps back?

From Dev

How to add test steps to a coded ui test?

From Dev

Selenium: How to stop execuing subsequest steps in @Test method after catching an exception using TestNG Listener?

From Dev

Struggling with toString method in Java

From Dev

How to create several implementation for the same named steps (behave)

From Dev

Calling a private method inside every JUnit before any test steps

From Dev

How to test if a method is const?

From Dev

How to run several test cases for one fixture in Google Test?

From Dev

How to update steps of Test Cases in Rally by using the Python API (pyral)

From Dev

How to unit test sql-stored steps in a camel route

From Dev

How to Use the SoapUI API to Create a Test Suite, Test Cases, Test Steps, and Assertions

From Dev

Serenity BDD with jUnit how to inject steps into setup method?

From Dev

How to test if a specific method is called

From Java

RSpec: how to test if a method was called?

From Dev

How to unit test POST method

From Dev

FakeItEasy - how to test virtual method

From Dev

How to test a method returning a Promise

From Dev

How to Test and mock an recursive method?

From Dev

How to test method of JavaFX controller?

From Dev

How to JUnit test a Method with a Scanner?

From Dev

How to test a method that draws to the canvas?

From Dev

How to unit test POST method

From Dev

How test method inside the class

From Dev

How to Unit test the following method

From Dev

How to test `alias_method`?

From Dev

How to test a model instance method?

From Dev

How to write a test method for createNewToken Method?

From Dev

Struggling to correctly implement a custom rails controller method

Related Related

  1. 1

    For loops: How could I repeat several steps?

  2. 2

    How to unwind segue several steps back?

  3. 3

    How to add test steps to a coded ui test?

  4. 4

    Selenium: How to stop execuing subsequest steps in @Test method after catching an exception using TestNG Listener?

  5. 5

    Struggling with toString method in Java

  6. 6

    How to create several implementation for the same named steps (behave)

  7. 7

    Calling a private method inside every JUnit before any test steps

  8. 8

    How to test if a method is const?

  9. 9

    How to run several test cases for one fixture in Google Test?

  10. 10

    How to update steps of Test Cases in Rally by using the Python API (pyral)

  11. 11

    How to unit test sql-stored steps in a camel route

  12. 12

    How to Use the SoapUI API to Create a Test Suite, Test Cases, Test Steps, and Assertions

  13. 13

    Serenity BDD with jUnit how to inject steps into setup method?

  14. 14

    How to test if a specific method is called

  15. 15

    RSpec: how to test if a method was called?

  16. 16

    How to unit test POST method

  17. 17

    FakeItEasy - how to test virtual method

  18. 18

    How to test a method returning a Promise

  19. 19

    How to Test and mock an recursive method?

  20. 20

    How to test method of JavaFX controller?

  21. 21

    How to JUnit test a Method with a Scanner?

  22. 22

    How to test a method that draws to the canvas?

  23. 23

    How to unit test POST method

  24. 24

    How test method inside the class

  25. 25

    How to Unit test the following method

  26. 26

    How to test `alias_method`?

  27. 27

    How to test a model instance method?

  28. 28

    How to write a test method for createNewToken Method?

  29. 29

    Struggling to correctly implement a custom rails controller method

HotTag

Archive