Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add framework-agnostic mock context for testing #351

Merged
merged 25 commits into from
Feb 1, 2025

Conversation

olisaagbafor
Copy link
Contributor

@olisaagbafor olisaagbafor commented Jan 13, 2025

Add MockContext for testing controllers

Adds a framework-agnostic MockContext implementation that allows users to easily test their Fuego controllers without setting up a full HTTP server.

Changes

  • Add MockContext implementation with all required interface methods
  • Add comprehensive testing documentation
  • Add example tests showing real-world usage

Closes #349

Implements a MockContext[B] type that allows testing controllers without
framework dependencies. Includes comprehensive tests and documentation.
README.md Outdated Show resolved Hide resolved
mock_context_test.go Outdated Show resolved Hide resolved
mock_context_test.go Show resolved Hide resolved
mock_context.go Outdated
Comment on lines 43 to 45
func (m *MockContext[B]) SetBody(body B) {
m.body = body
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not using a setter and not an exported attribute name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use a setter method instead of an exported field to maintain encapsulation and consistency with real implementations. This allows to add validation or logging in the future without breaking user code and ensures tests reflect how the code will behave in production.

Add MockContext[B] to help users test their controllers without framework
dependencies. Includes realistic examples and documentation.

- Move testing docs from README.md to TESTING.md
- Add mock context with getters/setters
- Add example user controller tests
- Use fuego_test package for end-user perspective

Closes go-fuego#349
@olisaagbafor olisaagbafor marked this pull request as draft January 13, 2025 19:18
@olisaagbafor olisaagbafor marked this pull request as ready for review January 13, 2025 19:25
Replace basic getter/setter tests with a real-world example:
- Add UserController with Create and GetByID endpoints
- Add UserService with mock implementation
- Show proper separation of concerns
- Demonstrate practical testing patterns
- Move testing docs from README.md to TESTING.md

This provides users with a clear example of how to test their
controllers using the mock context in a real-world scenario.

Closes go-fuego#349
TESTING.md Outdated Show resolved Hide resolved
TESTING.md Outdated Show resolved Hide resolved
TESTING.md Outdated Show resolved Hide resolved
TESTING.md Outdated Show resolved Hide resolved
TESTING.md Outdated Show resolved Hide resolved
@EwenQuim
Copy link
Member

EwenQuim commented Jan 13, 2025

Honestly I'm a bit disappointed as this is clearly AI generated and I don't feel that you've dig into the project. Sometimes your changes makes no sense and are a bit weird.

Maybe you're just learning software engineering and I'm really sorry in this case.

Also please don't resolve comments that are questions without answering to the questions. We'll see them haha 😉

@olisaagbafor
Copy link
Contributor Author

@EwenQuim ok, I will take them gradually and one after another, thank you for the feedback, honestly I appreciate them 🙏

@olisaagbafor olisaagbafor marked this pull request as draft January 14, 2025 08:52
@olisaagbafor olisaagbafor marked this pull request as ready for review January 14, 2025 12:23
@olisaagbafor
Copy link
Contributor Author

@EwenQuim please review ...

@EwenQuim
Copy link
Member

EwenQuim commented Jan 15, 2025

Thanks for your PR! 🥳

What was your AI?

@EwenQuim EwenQuim force-pushed the feature/mock-context-for-testing branch from fb4ca5d to 13dda83 Compare January 15, 2025 10:05
mock_context.go Outdated
Comment on lines 81 to 84
// SetBody stores the provided body value for later retrieval
func (m *MockContext[B]) SetBody(body B) {
m.body = body
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ccoVeille @dylanhitt what do you think about it? That is not very Go style, is it?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean?

I'm sorry but you drag me here an without much context 😅

Are you talking about the fact to have a setter here?

I'm unsure what is the functionality or need this method try to achieve

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I mean personally. I'm more used to the constructor of the mock setting all the needed values or as you said earlier just export the field.

Personally I expected this PR to be like if someone ran :GoImplements or whatever IDE's equivalent of that generation for all the needed func signatures. Maybe a few helpers. For instance just below this we have SetQueryParam that just sets UrlValues. This should also be updating OpenAPIParams in CommonContext if that's gonna be added in MockContext.

Actually wait a minute a call to QueryParam here isn't going to work. This implementation is just backpacking off of CommonContext's implementation. If you look at the code

func (c CommonContext[B]) QueryParam(name string) string {
	_, ok := c.OpenAPIParams[name]
	if !ok {
		slog.Warn("query parameter not expected in OpenAPI spec", "param", name, "expected_one_of", c.OpenAPIParams)
	}

	if !c.UrlValues.Has(name) {
		defaultValue, _ := c.OpenAPIParams[name].Default.(string)
		return defaultValue
	}
	return c.UrlValues.Get(name)
}

Well I guess it will "work" but it's gonna log the warning every time and I don't believe the Default case will ever happen either I don't believe. All that being said I'm describing a lot of features for a Mock lib already I wouldn't expect that to be fully implemented in a first pass.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess to sum up my last part if you're gonna backpack off the functions that CommonContext already supplies, they should "work". For instance

func (m *MockContext[B]) HasQueryParam(key string) bool {
	_, exists := m.UrlValues[key]
	return exists
}

above doesn't need to be implemented. CommonContext's implementation will suffice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback!

From the points here, then I propose to

  1. Make the fields public and remove unnecessary setters (more idiomatic Go)
  2. Remove redundant methods that CommonContext already handles
  3. Properly implement OpenAPI param handling

Should I go ahead and update the PR with these changes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me update them quickly

documentation/docs/guides/testing.md Outdated Show resolved Hide resolved
mock_context.go Outdated
Comment on lines 81 to 84
// SetBody stores the provided body value for later retrieval
func (m *MockContext[B]) SetBody(body B) {
m.body = body
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean?

I'm sorry but you drag me here an without much context 😅

Are you talking about the fact to have a setter here?

I'm unsure what is the functionality or need this method try to achieve

documentation/docs/guides/testing.md Outdated Show resolved Hide resolved
mock_context_test.go Outdated Show resolved Hide resolved
@olisaagbafor
Copy link
Contributor Author

@dylanhitt please check

@dylanhitt
Copy link
Collaborator

I will when I find the time.

@EwenQuim EwenQuim added this to the v0.18 milestone Jan 21, 2025
@olisaagbafor
Copy link
Contributor Author

good day @EwenQuim @dylanhitt @ccoVeille please am sorry for this long break. Just getting back from sick bed, am sorry for the long delays. Please, do I have any reservations on this?

…a request & controller with no body

Changed WithXxx to SetXxx for query parameters.
Removed the ...option pattern for declaring query parameters in the MockContext.
@EwenQuim
Copy link
Member

Just getting back from sick bed, am sorry for the long delays

No problem, be safe!

I added some commits to go towards what I think is right and added some examples, what do you think about it @dylanhitt @ccoVeille?


// Cookie returns a mock cookie
func (m *MockContext[B]) Cookie(name string) (*http.Cookie, error) {
cookie, exists := m.Cookies[name]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cookie needs it OpenAPIParam input

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I guess these are fine there. Unlike the other Params we don't really throw warning or anything here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it will be useful to set default values for example. Because defaults are defined in the route registration, not the controller.
But they'll be defined in 2 places : route registration & tests and it would be weird because nothing guarantees that it will be the same in these 2 different places.

Copy link
Collaborator

@dylanhitt dylanhitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe both Cookie and Header need their OpenAPIParam map entry. Could be wrong though


// SetHeader sets a header in the mock context
func (m *MockContext[B]) SetHeader(key, value string) {
m.Headers.Set(key, value)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same - need it's associated Param

It's not necessary to have the available fields and methods section in the testing guide, as it's already documented in the codebase & the godoc reference.
@EwenQuim EwenQuim merged commit b2965ee into go-fuego:main Feb 1, 2025
5 checks passed
@EwenQuim
Copy link
Member

EwenQuim commented Feb 1, 2025

Thank you @olisaagbafor for this PR!

@olisaagbafor
Copy link
Contributor Author

Thank you @EwenQuim @dylanhitt @ccoVeille for your utmost support here.
much appreciations!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a context usable in tests
4 participants