0

Say I want to test the behavior of the GUI while I follow a PassiveView approach. I also use the command pattern to handle the actions of the user. So given a PersonView and a PersonService with a savePerson(firstName,lastName) method, my command looks like:

class SavePersonCommand{
      
    public SavePersonCommand(PersonService personService,PersonView personView,
                             CommandExecutor commandExecutor) {
        //args go as fields
    }
    
    void execute() {
        String firstName = personView.getFirstName().trim();
        String lastName = personView.getLastName().trim();
        personService.store(firstName, lastName);

        //trimmed values will be shown to view
        commandExecutor.execute(RefreshPersonView.class);
    }
}

Mocking PersonService which talks to a database feels a normal thing to do. Mocking (fake-test double-mock whatever) view is what Martin Fowler suggests in the link above. However, the thing I end up having - a class tested only given mocks feels weird. Do I really benefit from that? Is it a code smell? Is it a normal phenomenon? I am also reading around a lot of discussions of kind "mocking is bad". Am I mocked by my mocks? (basically at 30:44)

From my perspective the tests of the commands which is essentially the whole GUI logic are simple. Clean, fast, straight forward and GUI framework/library independent. SavePersonCommand copies the X values from fields of the view and passes them to the service. I also, do not have to worry for other possible dependencies of the view. Like SpellCheckers, Painters or things like that.

As far as I know, we should test the code we fear. The GUI logic (the commands) is certainly something I am afraid of. But is it this a "correct" way of unit testing them?

And for the record, here is how my test would look like:

class SavePersonCommandShould{
    @Test
    void save_person_with_properties_from_view_and_then_refresh_the_view() {
        PersonService service = mock(PersonService.class);
        PersonView view = mock(PersonView.class);
        CommandsExecutor executor = mock(CommandExecutor.class);
        
        
        when(view.getFirstName()).thenReturn("mike ");
        when(view.getLastName()).thenReturn("thedeveloper"+System.lineSeparator());
        
        new SavePersonCommand(service,view,executor).execute();
        
        verify(service).store(eq("mike"),eq("thedeveloper"));
        verify(executor).execute(eq(RefreshPersonView.class)); 
    }
}

On the other side, if I use the implementation of the view I have the benefit of testing more things at the same time. Things like "do the personView.getFirstName() returns the value of the fistNameTextField". Plus, the testing code is closer to what happens in production. But at the same time, I get dirty tests since views may require complex initiation.

5
  • Is PersonView and PersonService classes or interfaces? Commented Mar 25, 2021 at 23:05
  • @GregBurghardt They can be anything. If they are interfaces, they are only for test purposes. In production there is only one PersonService, and one PersonView as well.
    – George Z.
    Commented Mar 25, 2021 at 23:19
  • It makes a big difference. Interfaces can be easier to mock than abstract classes or concrete classes. Commented Mar 25, 2021 at 23:45
  • @GregBurghardt I know. But i dont know how this affects my question. Say that they are interfaces and i can mock easy, then what? Say they are not interfaces and i cant mock "easy", then what?
    – George Z.
    Commented Mar 25, 2021 at 23:59
  • If they are not interfaces, and you cannot mock them easy, then refactor SavePersonCommand to use interfaces, then mock them. I just posted an answer, if you want to take a look. Commented Mar 26, 2021 at 0:07

2 Answers 2

3

However, the thing I end up having - a class tested only given mocks feels weird.

This isn't as weird as you might think. I would argue that your tests are written well for the code being tested. A proper unit test should verify a single unit of behavior — with "unit" being a bit ambiguous. In your case, the unit of behavior is the execute method. This method mostly coordinates with other objects. Those other objects are stubbed or mocked, so you aren't testing the logic of those other objects, but you are testing the logic of the execute method. And that is the whole point.

The real PersonView, PersonService and CommandExecutor objects should have their own tests, where they are isolated from the rest of the application. You should not test the PersonView logic in tests verifying the behavior of the SavePersonCommand. There is nothing wrong with verifying that the view gets refreshed, or verifying that the person gets stored, but that should entail assertions on your mocks, not the database or a real application view.

The real frustration stems from the amount of time you spend setting up your mocks. Coordination logic is prone to complex unit test setup, because the whole reason classes like SavePersonCommand exist is to coordinate behavior between two or more complicated objects.

If you want fast tests for things like SavePersonCommand, you must mock. If you do not want to mock, you must suffice with slow, brittle end-to-end tests. The price of fast tests is the slow speed at which they are written, but if you write an end-to-end test that verifies this behavior with the same level of precision, you'll spend just as much time writing end-to-end tests. I would rather have fast executing tests for the same effort.

1
  • I mostly write these tests using TDD as a workflow. If I use the concrete implementation of (say) PersonView, it really messes my flow of thinking when I see its constructor require a Painter, a ToolTipCreator and a BorderFactory. Setting up the mock object, avoiding meeting this UI dirtiness, keeps my head on the test case. But still, when I see the whole thing when I am done, it feels weird.
    – George Z.
    Commented Mar 26, 2021 at 9:39
2

This methodology certainly unit tests your class and is widely used. But I share your concerns and would not use it for this case.

The root problem is that this class doesn't really do anything, its a void method which is just orchestrating the two dependencies.

When you inject the mocks like this you are simply testing the internal logic of the class rather than the desired behavior of the system. This leads to brittle tests where the mock setup is as likely to be the cause of a failure as a bug.

I would move the testing up a level or two to where you can have a more meaningful test. ie

  • If I execute a a SavePerson command and then check the people in my database I find the new person

OR

  • If I click save the form resets and a "saved" tick appears.
5
  • 2
    Checking if the person is in the database doesn't belong in PersonService tests? Also, won't I end up with a slow test suite? Say the GUI has 100 commands (some of them are based on a use case - like SavePersonCommand and some others are shared among use cases - like RefreshPersonViewCommand). Isn't your suggestion a more integration/acceptance testing? If I take this approach will I be capable of knowing what exactly broke?
    – George Z.
    Commented Mar 25, 2021 at 22:43
  • Also, about the simplicity of the Command, I just threw an example. Some views have like 30 fields. I feel I am doing something if I test if the values of these fields sent to backend correctly.
    – George Z.
    Commented Mar 25, 2021 at 22:43
  • You're not really testing anything though. because your mock setup here is as error prone as your test subject. ie you the service might have a bug where is puts first and second name in the wrong order or misses one. but your mock could have the exact same bug. To test properly you need to have output, and in this case it would be retrieving a person object from the repository and comparing it to the one you have in the view
    – Ewan
    Commented Mar 26, 2021 at 11:09
  • @Ewan - I disagree. The code does trim the input data. It's trivial, yes, but it does do something and the test checks that it performs it correctly, at least for the nature of the white space that's set up in the Mocks. It tests what the method does, and as part of a larger network of tests it has its place. The assumption HAS to be that the classes which are mocked have their own tests which would catch the kind of problems that you describe. The key issue is to keep in mind what the "code under test" is. It's not the functionality in the mocked classes.
    – Dave
    Commented May 22, 2021 at 15:15
  • perhaps it is more actuate to say the "interface does nothing" and that you should test the the interface. atm you can only test for a side effect. If you refactored to return the trimmed name then you could have a sensible unit test for the trimming but as it stands, if you take the general approach of testing the internal mocked calls you will end up with many brittle and unhelpful tests
    – Ewan
    Commented May 22, 2021 at 15:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.