Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

This sounds pretty similar to my experience with UI testing. What kinds of tests do you write instead? And how have they worked out for your team?


We tend to create a "view model" object which maintains the state of the UI and interacts with the server. The actual view just forwards commands to this object, and after a command happens the view always re-renders based on the current state of the model. We do this on iOS but I've been pushing for us to do it with React as well since you get the built-in state rendering for free.

So all of the tests look something like this (here's a UI form):

  // Arrange
  let searchResponse = { usernameTaken: false };
  let networkService = NetworkServiceTestDouble(searchResponse);
  let vm = UserFormViewModel(
    networkService: networkService
  );

  // Act
  vm.username = "newusername@email.com";
  vm.checkUsernameAvailable();

  vm.submitForm();

  // Assert
  expect(networkService.calls[0].arguments[0]).to.eq({
    username: "newusername@email.com"
  });
So you see the view model is kind of like a Page Object, in that it has an API which represents the user's interaction. But, in this case the view model is what's used in the actual production code, it's not just a testing helper. So you just invoke the API as the user would when interacting with the feature, and verify that the correct data gets sent to the server when the form is submitted. After all, the client's job is primarily to interact with the server and maintain state. The feature is functional if the correct data is sent to the server.

Then, let's say you're in React, hooking up this view model is quite easy:

  import { UserFormViewModel } from "./UserFormViewModel";

  let vm = UserFormViewModel(realNetworkService);

  function UserForm() {
    return (
      <button onClick={vm.submitForm()}>Submit</button>
    )
  }
The buttons / inputs just get wired up to the appropriate view model functions and properties, and you can have the component's state just be the view model's state. The view is then just entirely about appearance and forwarding events.

I'll then write some tests that actually render the UI, but they're just sanity checks. How often do you get a bug where you failed to call the correct function in response to a button click? The logic and wiring in response to all of the events is the more interesting part.

Edited to improve the code formatting a bit.


This is nice, thanks for taking the time to explain and write it all out. I like UI testing because I dislike maintaining mocks and stubs all over the place (and haven't found a way to write lower-level tests that don't rely on them heavily), but this approach definitely seems interesting. I like that it also forces your views to stay simple and just call simple functions. A problem resulting from not calling a handler or calling the wrong one would be immediately obvious, the tests really deal with checking what happens when that handler is called.. this seems like a great way to isolate that and test it without the pain of selenium et al! Cool pattern, thanks for sharing.


Agreed about the mocks and stubs. I dislike it too, but it's unfortunately pretty necessary if your frontend and backend are written in separate languages. If you are writing in the same language, you can actually wire up the frontend and backend together and you don't need to use test doubles! That's an architecture I've been playing around with, but unfortunately not at work.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: