loading...

re: Is easier to do TDD for your dependencies after you have designed their API using mocks VIEW POST

FULL DISCUSSION
 

Code is not so clear.

Why the .call, no calling?
What's the relation of two test code, cannot see the API relation.

Could you explain more?

 

Hi change, I will try to explain a little more...

Why the .call, no calling?

I am not sure if I am understanding this question... but if you are asking...

Why CityGeocoder.call is not called in the first test file...

Well, first I tried to explain the expected behavior for the project.city method...

projects.map do |project|
  project.city
end

# => [
#   "Monterrey, Nuevo León",
#   "Ciudad de México, México",
#   "Corregidora, Queretaro",
#   ...
# ]

And in our tests for the use case function Projects.get_projects_list we are testing the each returned project should have the city geocoded from the latitude and longitud.

Thats why I tried to be explicit in the tests...

describe "has the project city..." do
  it "geocoded from the lat-lng" do
    project = project_for_list_from(latitude: 1234, longitude: 2345)
    expect(project.city).to eq "City geocoded from lat: 1234, long: 2345"
  end
end

Trying to match the values of the latitude and longitude to the message in the expectation... "City geocoded from lat: 1234, long: 2345"

If you see the expected message, is not really the kind of response that we said in the example... Maybe this is not very clear =S, but here I am trying to tell that our use case function will not do anything but to ask the given city_geocoder for the city and then return just that....

That's why I am trying to express here also, by creating the mock in the same file and pass it as a dependency in the config for the Projects.get_projects_list function.

    class FakeCityGeocoder
      def self.call(lat, lng)
        "City geocoded from lat: #{lat}, long: #{lng}"
      end
    end

    def get_projects_list(projects, opts = {})
      config = {
        #...
        projects_store: projects_store_with(projects),
        city_geocoder: FakeCityGeocoder
      }.merge(opts)

      Projects.get_projects_list(config)
    end

And I am not explicitly doing this...

expect(city_geocoder).to receive(:call).with(1234, 2345)

Because (although that call may have side effects) I am considering as a query without side effects, and I consider that doing that for query messages, is like testing implementation details.

As a note: I have another post about that topic and I also like the proposal of Sandy Metz in his talk The Magic Tricks of Testing

What's the relation of two test code, cannot see the API relation.

About the relation between the two test files

Well, is hard to tell all what I was thinking jeje... But I think that the most important thing is that... Is very common to try to write wrappers around the services that we are going to use, so maybe instead of proposing a dependency like....

CityGeocoder.call(lat, lng)

Or maybe other similar options like...

Geocoder.city_for(lat, lng)
Geocoder.find_city(lat, lng)

We may be tempted to write a wrapper just around our geocoding services, and end with something like this...

Geocoder.geocode(lat, lng) => {"results" => { "address_components" => [{"types" => [...]]}

Directly injected in our use case functions... but if we do something like this thinking in what we have instead of what we need we are creating a big coupling between our business logic and an external service that will change for different reasons that our business logic.

And also... Sometimes when we create more custom dependencies, sometimes is not obvious....

  • How you should test them
  • Or, if you again should write mocks for their dependencies..
  • Or, if write integration tests is the right thing
  • Or, that an integration test should call all the layers in our application.
  • Or, that integration tests and TDD don't go together...

That's why I am showing the tests and implementation of our "real" CityGeocoder to try to show and tell that...

  • You can tests your dependencies writing integration tests.
  • That they can be called integration tests, because tests if they are returning the right result in integration with the real service.
  • That is ok to write them, and maybe easier now that you have reduced the scope of testing
  • And that you can also do TDD with them =)

... And I think that's all... I hope that the message is now more clear =)

Thanks for asking!... sometimes is hard to try to give the right message, in a short message =S, but is very helpful to have feedback on what can be explained better =)

 

I know your idea is writing a mock test to get a tested API, then do real test when needed.

Your sample is really a little complicated for me, I read several times and not follow fully.

Ups... sorry 😳 ... which part do you think that is more complicated?

Hi Chenge... I tried to simplify and remove stuff from the example, that I think is not need to share the idea.

Thanks, I try to read once more. =)

It's clear more now.

I think best include full code or simplify code to be full. If you miss some method that'll be hard to follow.

Code of Conduct Report abuse