3 ms·
> Providing working fakes for every dependency isn't always realistic. I don't know what a fake is. Is that what mockery gives you? Per the dictionary, fake is
by randomdata 2y ago
> Providing working fakes for every dependency isn't always realistic.
I don't know what a fake is. Is that what mockery gives you? Per the dictionary, fake is defined similar to mock, but without the no deception condition, so I suppose that adds up.
There are also stubs, which is defined as something that is truncated or a part of. Which, as it pertains to software, is an implementation that implements some kind of bare minimum to satisfy the interface – often returning canned responses, for example. Mockery arguably also fits here, except the assertion part, which is something else. But I guess that's where fake comes in to draw that differentiation?
> But if you those are params to some external RPC suddenly this actually becomes pretty useful.
Sure, a stub might check the inputs and return an error if some condition is not met, without needing to implement the service in full. This remains true to what the real service would also do.
But that's not what mockery does. It just blows up spectacularly if something wasn't right. That doesn't really make any sense. That is now how the real implementation works. Not only that, but in the case of mockery, its documentation advises that you put the dependency logic in the test. How silly is that? Now when you replace Stripe with Line all your tests are broken. If you used a stub, you merely change the stub to match and you're good to go. This way the tests remain pure, as they need to as they are the contract you make with your users. Changing the contract is unacceptable.
And for all that, it doesn't seem to serve any purpose. But we did ask the other guy for a concrete example (i.e. code) to show where one would want to use it. Looking forward to it.
- aleksiy123 2y agoHere, I'll provide an example I generated with chatgpt with some prompting. type Server struct { Payment services.PaymentService Storage services.StorageService Quota services.QuotaService Auth services.AuthService Notification services.NotificationService } // TransactionRequest is the request structure for a purchase type TransactionRequest struct { UserID string `json:"user_id"` AuthToken string `json:"auth_token"` ProductID string `json:"product_id"` Price float64 `json:"price"` Currency string `json:"currency"` File []byte `json:"file"` Key string `json:"key"` } // TransactionResponse is the response structure after processing a transaction type TransactionResponse struct { Status string `json:"status"` Message string `json:"message"` } // HandleTransaction handles the incoming transaction request func (s *Server) HandleTransaction(w http.ResponseWriter, r *http.Request) { var req TransactionRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } // Authenticate user userID, err := s.Auth.Authenticate(req.AuthToken) if err != nil { http.Error(w, "authentication failed", http.StatusUnauthorized) return } // Check and update quota allowed, err := s.Quota.CheckQuota(userID) if err != nil || !allowed { http.Error(w, "quota exceeded", http.StatusForbidden) return } s.Quota.UpdateQuota(userID, 1) // Assume updating quota by 1 unit per transaction // Process payment transactionID, err := s.Payment.Charge(req.Price, req.Currency, "payment-token") if err != nil { http.Error(w, "payment failed", http.StatusInternalServerError) return } // Upload file to S3 fileURL, err := s.Storage.Upload(req.File, userID + "-files", req.Key) if err != nil { http.Error(w, "file upload failed", http.StatusInternalServerError) return } // Send notification notificationMessage := "Your purchase has been processed successfully." s.Notification.SendNotification(userID, notificationMessage) response := TransactionResponse{ Status: "success", Message: "Transaction processed successfully: " + transactionID + " File uploaded to: " + fileURL, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } It is still contrived obviously. But what I would like to show what happens if use stubs. You could pass anything into the dependency parameters and the test would pass. Mock allows you to "lock" in the implementation. Its tightly coupled yes but sometimes that is exactly what you want. In this example that would be the userId, the bucket name, the key. Mock allows you inject faults in the test setup. Lets say I want to test the error handling logic. I could do it with multiple types of stubs but if you squint its starting to look the same. On the topic of Fake vs Stub vs Mock. Real or Fake is the ideal solution here but not always viable due to time/practical constraints. Stubs can be useful but don't give you fine grained control.
- randomdata 2y ago> You could pass anything into the dependency parameters and the test would pass. I don't follow. Under no circumstance would the test pass under invalid inputs, be it whether you use mocks, stubs, or even mockery (fakes?). - Obviously it cannot pass while using mocks – a mock matches the real thing to every last detail. If it will fail while using the real thing, it will fail here as well. - Obviously it cannot pass while using stubs – a stub matches the real thing, partially, to at least the minimum amount necessary for the sake of testing. If it will fail while using the real thing, it will fail here as well. - Obviously it cannot pass while using mockery – mockery does not match the real thing in any way, but does require you to specify matching inputs with failure when they don't match. If it will fail while using the real thing, it will fail here as well. I don't know what you are imagining, but I'm not convinced it is a thing. I mean, you could make it a thing - you can do anything your little heart desires, but there would be no reason to ever make it a thing. Maybe we need some concrete tests to better illustrate what you are talking about?
- aleksiy123 2y agoWe seem to be using different definitions of stub and fake. What you call a stub is generally known as a Fake. https://stackoverflow.com/questions/3459287/whats-the-difference-between-a-mock-stub https://stackoverflow.com/questions/3459287/whats-the-differ... Stubs do not react based on their input parameters. However, even fakes have the issue of fault injection. I don't want to just test valid inputs. I want to make sure I pass the correct inputs for the request parameters through to the dependencies. If pass 100.00 in the request but in my impl I pass 0.00 to the stub. The test will pass because the input is valid. But the implementation is not correct. Also, mockery is just an alternative mocking api. There is nothing special here. How would you test the case where the quota update fails with a stub? Here is mockery example again generated with chatgpt. func TestHandleTransaction_QuotaUpdateFails(t *testing.T) { // Create the mock services mockAuth := new(mocks.AuthService) mockPayment := new(mocks.PaymentService) mockStorage := new(mocks.StorageService) mockQuota := new(mocks.QuotaService) mockNotification := new(mocks.NotificationService) // Setup expectations mockAuth.On("Authenticate", mock.Anything).Return("12345", nil) mockQuota.On("CheckQuota", "12345").Return(true, nil) // Quota check passes mockQuota.On("UpdateQuota", "12345", mock.AnythingOfType("int64")).Return(errors.New("quota update failed")) // Quota update fails // No need to mock payment and storage as they should not be called if quota update fails // Create the server with mock services server := &Server{ Auth: mockAuth, Payment: mockPayment, Storage: mockStorage, Quota: mockQuota, Notification: mockNotification, } // Create a test request transaction := TransactionRequest{ UserID: "12345", AuthToken: "valid-token", ProductID: "product123", Price: 100.0, Currency: "USD", File: []byte("file data"), } requestBody, _ := json.Marshal(transaction) request := httptest.NewRequest(http.MethodPost, "/transaction", bytes.NewReader(requestBody)) responseRecorder := httptest.NewRecorder() // Call the endpoint server.HandleTransaction(responseRecorder, request) // Check the results assert.Equal(t, http.StatusInternalServerError, responseRecorder.Code) response := TransactionResponse{} json.NewDecoder(responseRecorder.Body).Decode(&response) assert.Equal(t, "error", response.Status) assert.Contains(t, response.Message, "quota update failed") // Assert that all expectations were met mockAuth.AssertExpectations(t) mockQuota.AssertExpectations(t) // Assert no calls to payment and storage mockPayment.AssertNotCalled(t, "Charge", mock.AnythingOfType("float64"), mock.AnythingOfType("string"), mock.AnythingOfType("string")) mockStorage.AssertNotCalled(t, "Upload", mock.AnythingOfType("[]uint8"), "bucket-name", "key-name") // No notification should be sent mockNotification.AssertNotCalled(t, "SendNotification", "12345", mock.AnythingOfType("string")) }