0

Well, we all know the main concept of TDD: write a failing test, then implement the testing component to make your test pass.

I've been working on a personal project in Clean Architecture following TDD, and I'm gonna start testing some basic use cases that just CRUD some entities. These interactors classes use presenters to show some information and I came across to an implementation that I probably have to write before writing tests and mocks: the presenters.

I'll show an example of use case without any implementation:

import Interactor from "@application/common/interactor";
import Presenter from "@application/common/presenter";
import { CreateCityRequestDTO } from "../common/city-dtos";
import { CreateCityInteractorGateway } from "../common/city-gateway";

interface CityData {
  cityCode: string;
  cityName: string;
  stateName: string;
  pop: number;
}

interface CreateCityInteractorParams {
  createCityInteractorGateway: CreateCityInteractorGateway;
  createCityInteractorPresenter: Presenter<void>;
}

export class CreateCityInteractor extends Interactor<CreateCityRequestDTO, CityData> {
  private gateway: CreateCityInteractorGateway;

  constructor(params: CreateCityInteractorParams) {
    super(params.createCityInteractorPresenter);
    this.gateway = params.createCityInteractorGateway;
  }

  protected async execute(input: CreateCityRequestDTO): Promise<CityData> {
    throw new Error("Method not implemented.");
  }
}

I know I can do some fake gateway implementations, just to satisfy the data storage functions, but I'm not really sure about the presenters. So my question is:

Develop presenters before testing use cases is the only way here? Am I doing something wrong and not following the principles from CA and TDD?

I'm gonna leave this project's repository in case you have any question or suggestion: https://github.com/gabriel-abn/green-manager

1
  • 1
    Fo the same reason you don't start building a house by the roof, you should not start coding CA by the outer layers. Start with core (domain and business). The core will give you many ports you will have to "mock" at some point. But let'em emerge first.
    – Laiv
    Jul 7 at 11:24

1 Answer 1

3

You can have your presenter built bit-by-bit as you're adding tests.

But more importantly, in principle, you don't need to write any of these extraneous implementations beforehand, and you don't need to have the presenter built at all by the time your last interactor test is written - that's the whole point.

It is enough to just make the simplest mock/fake of the presenter you can, that returns canned results for the one specific test case it's used in, and nothing else - it has no behavior, it does nothing but serve the test case. If it needs to simulate doing some processing of some input, you just make it return what you expect it to return - you don't do the actual processing. It doesn't have to have all of its methods implemented (heck, if you weren't using TypeScript, it wouldn't need to have all of its methods present on the object at all). It has no other purpose and can't be used for anything else.

If you think of the test structure as "arrange-act-assert", you can create your presenter as an ad hoc object right there in the arrange step. In a different test case, you can make a completely new ad hoc mock, that serves as a setup for that test case.

Optionally, you could streamline this by introducing some kind of a factory that helps you create these mocks, and maybe add a capability to spy on certain methods, if that's required to check the contract of the class you're testing. Or you can use a 3rd-party library that creates these mocks for you.

Remember, you're not testing the presenters here, they are just part of the test case setup. They just serve to set up the initial conditions. You're testing the behavior of the interactor, which among other things includes the interactor-side of the contract between your interactor and any possible presenter. The idea is that these tests provide a certain level of confidence that, if you plug in an actual presenter elsewhere, the interactor will be well-behaved and will work with the presenter in a manner specified by these tests (and will otherwise work as expected with client code, but that's not the focus of the discussion here).
The tests are, in a sense, really a stand-in for client code that's going to be working with the interactor, and that's going to choose and inject a concrete presenter into the interactor (i.e., the tests systematically exercise a number of simple scenarios that client code can use to build its own behavior out of, and they let you run your interactor without actually having any client code, or a working presenter).

If you want to tests the abstract behavior of the presenter itself (and you may or may not want to be bothered with that), that's a completely separate set of tests, and it should pass for any implementation of the Presenter interface (that's intended to be plugged into the associated interactor). Basically, if you make a new presenter meant to work with this particular interactor, and you plug it in into this set of tests, and they pass, it should provide a certain amount of confidence that your presenter is implemented properly and can be used in any place where the interface is expected (is substitutable in the LSP sense).
These tests are a stand-in for the interactor itself (they systematically exercise a number of simple scenarios that the actual interactor can use to build its behavior out of, and they let you run the presenter code without actually having a working interactor).

That's the principle: testing the abstract behavior, the contract - not the implementation. But, you are free to deviate from it if you think that'll make things easier without incurring a cost, or if you feel there's no need to be so rigorous (e.g. if the presenter is fairly simple, and you're fairly certain there's only ever going to be a single implementation of it, you could choose not to mock it in your tests).

3
  • "you don't need to write anything beforehand" I'm not sure whether this is semantic pedantry because you intended to include it in the test-writing phase, but deciding on the interface of the unit you're about to write tests for is something that you can and should do in advance. I don't just mean a literal interface btw, the collection of public members of a class fit the same bill in this regard.
    – Flater
    Jul 7 at 7:26
  • @Flater - By "anything" I meant "any of these extraneous implementations" that the OP was concerned about - I guess I'll change the text to that to make it clearer. Jul 7 at 14:24
  • @FilipMilovanović thanks for taking some time trying to explain my question... I'll continue studying and i'mma try figure out what I'm leaving behind on my code and in my notes... Jul 8 at 15:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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