Coder Social home page Coder Social logo

Comments (8)

oskardudycz avatar oskardudycz commented on August 15, 2024 2

@gionapaolini, because of the explanation I gave above, I will close the issue and pull the request. If you still want to continue the discussion, I could clarify or suggest something more; feel free to send the feedback.

Even though this is not the change that we'd like to introduce, I'd like to thank you for your contribution. We're open to the further one. I'd suggest starting with posting the issue and discussing the details of the potential code contribution.

from eventstore-client-go.

gionapaolini avatar gionapaolini commented on August 15, 2024 1

I wasn't aware of all the online discussions on the topic (to mock or not to mock)
I am not convinced yet, but I just quickly read few things and I guess I will need to go a bit deeper to make an informed decision.

In the meantime thank you for the info

from eventstore-client-go.

oskardudycz avatar oskardudycz commented on August 15, 2024 1

@gionapaolini, I agree with @ylorph. We're trying to say here that the unit testing mocked interface would not give you the guarantees you (probably) expect to get from the test. Such a test would be more testing the implementation rather than actual functionality. By that, I mean it will check the coding mechanics if you call the specific set of methods.

As I explained in my initial answer, the implementation specifics may differ a lot between the mock assumptions and what will happen in the actual database connection. Thus, some scenarios are great for unit testing, like business logic, etc. However, it's better to test using real tools (e.g. databases). Indeed the performance may be a bit lower, but then you're getting much better certainty that you're code is doing what you expect it to do. I think that such confidence is much more important than cutting some milliseconds in the test run.

Of course, that requires getting the right balance. I'm not suggesting testing all the code with integration tests. You could test the method that you pasted with the set of integration tests to make sure that it's working as expected. Then the code using it can trust that set of tests, don't repeat them and use mocks for your interface. The most important thing for me is to ask myself what this test is trying to verify and check. If it's just checking if I expectedly called some method, then that's not the most crucial test for me, as it's checking how I code, not how the stuff works.

I wrote recently on a similar topic in my article https://event-driven.io/en/i_tested_on_production/.

Check also:

from eventstore-client-go.

oskardudycz avatar oskardudycz commented on August 15, 2024

@gionapaolini, could you explain more of your use case? I'd like to understand why you need to mock the reading result?

Our general recommendation is to split the responsibility between the domain/application code and infrastructure part. By having that, you can unit test your business logic, then have a set of integration tests that will ensure that all of the components joined together work as expected. A potential solution is to use Repository Pattern, see an excellent article on that topic from @roblaszczak: https://threedots.tech/post/repository-pattern-in-go/.

Also, for instance: If you're doing Event Sourcing, then typically, you'd like to get the current state from events, so instead of returning EventStoreDB class, you could return the state.

We don't recommend mocking the client classes. Besides what I wrote above, the appending and reading logic from the big picture perspective may look simple and easy to mock. However, the implementation details like connection management, serialisation, clustering, etc., can cause (in edge cases) a significant difference in the final implementation. In the worst case, it may end up in a situation where all unit tests are green, but after deployment, features are failing.

We provide a Docker image (see more in our docs: https://developers.eventstore.com/server/v21.10/installation.html#docker) that you could use in your CI. It is relatively lightweight and shouldn't be a considerable overhead. You can consider using TestContainers, which makes the test setup easier (see https://golang.testcontainers.org/).

If I didn't cover your use case, feel free to expand on that, then I could try to give further suggestions.

from eventstore-client-go.

gionapaolini avatar gionapaolini commented on August 15, 2024

Hi @oskardudycz , thanks for the detailed answer.

Please let me know if I misunderstood something, your main concerns are that:

  • the domain layer is not decoupled enough from the application layer
  • mocking the client would hide possible failure due to the real implementation

To address the first point, I am already using a repository pattern:

I have an interface for the repository, which has a Save(menu Menu) error and Get(id uuid.UUID) (Menu, error) methods.
This interface is used in the domain logic.
Then I have an implementation specific to the EventStore, that implements the Save and Get methods.

Here you can see the implementation for the GetMenu:

func (eventStore MenuEventStore) GetMenu(id uuid.UUID) (aggregates.Menu, error) {
	menu := aggregates.Menu{}
	streamName := "Menu-" + id.String()
	options := esdb.ReadStreamOptions{}
	stream, err := eventStore.db.ReadStream(context.Background(), streamName, options, 200)
	if esdbError, ok := esdb.FromError(err); !ok {
		if esdbError.Code() == esdb.ErrorResourceNotFound {
			return aggregates.Menu{}, ErrMenuNotFound
		}
	}
	defer stream.Close()
	for {
		eventData, err := stream.Recv()
		if errors.Is(err, io.EOF) {
			break
		}
		if err != nil {
			return aggregates.Menu{}, err
		}
		event := aggregates.DeserializeMenuEvent(eventData.Event.Data)
		menu = menu.AddEvent(event)
	}
	return menu, nil
}

Currently the logic to re-construct the Menu is included in the GetMenu, which I understand it's debatable (I could just return all the events and delegate the reconstruction to another object, maybe a factory/builder), but even without the reconstruction part, it is worth to be covered by a unit test since there is more than only client calls.

To address the second point, I understand the implementation of the client is complex, but that is exactly why I want to mock it.
I want to test my code, not the actual eventstore client implementation (for which I agree integration tests will be needed).
Here I'm focusing on writing unit tests and they should be fast and with no external dependencies.

Let me know if something is still unclear or you have further suggestions for my case

from eventstore-client-go.

ylorph avatar ylorph commented on August 15, 2024

In the code you showed, testing the logic of reconstructing a menu ( menu.AddEvent(event) ) can be done in specific tests, and no connection is required for that

What I'd certainly delegate somewhere else is the stream name ( either a convention or some code handing out the stream name for the specific entity type )
That also can be covered by test not requiring a connection

what is then left to test are infrastructure code concerns, that are better tested against a real connection than a mock .

from eventstore-client-go.

gionapaolini avatar gionapaolini commented on August 15, 2024

Hi @ylorph thank you for your input.

I don't quite understand why it's preferable to test with a real connection.

The objective of a unit test is to ensure the logic of my code works as expected, not the logic behind the client. I want to ensure that the client gets called correctly with some specific parameters, and that the results are correctly processed.

Even if we strip out all the rest, we are left with quite some logic that I need to cover.

func (eventStore MenuEventStore) GetEvents(streamName string) ([]*esdb.ResolvedEvent, error) {
	options := esdb.ReadStreamOptions{}
	stream, err := eventStore.db.ReadStream(context.Background(), streamName, options, 200)
	if esdbError, ok := esdb.FromError(err); !ok {
		if esdbError.Code() == esdb.ErrorResourceNotFound {
			return []*esdb.ResolvedEvent{}, ErrMenuNotFound
		}
	}
	defer stream.Close()
        events := []*esdb.ResolvedEvent{}
	for {
		eventData, err := stream.Recv()
		if errors.Is(err, io.EOF) {
			break
		}
		if err != nil {
			return []*esdb.ResolvedEvent{}, err
		}
		events = append(events, eventData)
	}
	return events, nil
}

I understand I can test directly with a real connection, but the running time of the test grows a lot when doing so (from 0.003s to 0.1, for a test that saves and gets). And if we follow this principle, then we would (almost) never need Mocking.

I am not saying it should not be tested with a real connection, just that I think we are talking about different scopes (unit vs integration test)


Edit: One specific case I can think of is, what if I want to test how the app behave in case of issues with the eventstore (connection errors, etc..) with a mock I can easily simulate that

from eventstore-client-go.

ylorph avatar ylorph commented on August 15, 2024

unit vs integration test

Indeed,
the logic of events = append(events, eventData) is unit tests..
anything that implies a connection is integration

with a mock I can easily simulate that

You can't simulate all the possible way the connection / server / network in between will fail.

Additionally, I know of no database out there that provide interface to their connection for mocking reason, because of my above statement.

from eventstore-client-go.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.