Mocking

Introduction
  • The session begins with an informal greeting and establishing the atmosphere.

  • Questions are posed to gauge the progress of the students on assignment two.

    • Interactive participation: Hands raised for those who have started and those who are facing challenges.

Assignment Two Overview
Student Progress
  • Many students have started the assignment; some reported having difficulties.

Areas of Difficulty
  • Common challenges include:

    • Bug finding: Students are encouraged to share specific bugs encountered.

    • Cloning the repository: Assistance will be provided through an upcoming video tutorial.

    • Pigeon test: Deliberately includes bugs for learning purposes; experimentation and debugging strategies are encouraged.

Debugging Tips
  • If debugging leads to breaking another test, the recommended action is:

    • Undo the changes to identify what went wrong. Fixing should ideally involve proper refactoring.

Introduction to Mocking
Definition of Mocking
  • Mocking: The process involves creating test doubles or mock objects that replicate the behavior of real objects in a controlled way for testing.

    • “Mock” can also referred to a “test double.”

    • Illustrates concepts with the analogy of a stunt double: someone who stands in for the real thing.

  • Applications of mocking in system testing, particularly when real-world interactions are involved.

Test Mock Concept
  • Mock objects can either mimic the behavior of real objects or interfaces, typically implemented in the following ways:

    • Object Simulation: An instance that behaves similarly to the real object.

    • Functional Simulation: A function that imitates the real function's interface.

Payment Processor Class Example
Payment Processing Logic
  • Example setup: Description of a payment processor class that utilizes a credit card service.

    • A method to make payments checks card validity and communicates with the credit card service.

    • Validation check: Returns false if an expired card is used.

Testing the Pay Method
  • Discusses a specific test case for making payments with a hypothetical expired credit card.

  • Two testing approaches are described:

    • Testing with guaranteed conditions (like using an expired card should yield expected outcomes).

    • Writing broader tests that involve the real transaction process which incurs costs when using real services. This leads to expensive unit tests.

Mocking to Prevent Side Effects
  • Importance of Mocking: Inserts fake dependencies to prevent tests from interacting with real-world side effects.

    • Failure to employ mocking results in tests that could execute real transactions (i.e., charging a credit card).

Preparing for Mocking
Challenges of Direct Object Creation
  • Issues arising from hidden service dependencies within classes.

  • Discusses Dependency Injection as a solution to enable easier mocking by allowing the payment processor to accept any credit card service as an argument.

Implementing a Mock Credit Card Service
  1. Define a credit card service interface with a charge method.

  2. Create a mock implementation of this service for testing purposes, enabling the payment processor to utilize this mock instead of the real-world credit card service.

Concepts of Good Software Testing Practices
Good Practices for Testing
  • Encourages the practice of:

    • Test-driven development (TDD): Write tests before implementing functionality to ensure testability from the outset.

Types of Mocks

Overview of Mock Types

  • Three main types: fakes, stubs, and validators.

    • Fakes: Close approximations of the real object, imitating behavior as realistically as possible.

      • Example: A FakeCreditCardService that processes payments internally without involving a real bank, perfect for integration tests that need realistic behavior without real transactions.

    • Stubs: Hard-coded responses to simulate specific scenarios.

      • Example: A StubFileSystemService that always returns true for a writeFile call or always DiskFullError for a specific test case, useful for testing error handling without needing a physical disk.

    • Validators: Test interactions to validate that methods were invoked under the right circumstances, although their use should be limited to prevent brittleness.

      • Example: A ValidatorLogger that records if logError was called with a specific message within a test, ensuring error reporting logic is triggered.

Implementation Examples and Concepts
File System Service Example
  • Discusses an example where a file system service is mocked for testing.

  • Emphasizes the need for mocks to faithfully replicate expected behaviors such as handling storage limits.

  • Edge Cases: Use stubs to create controlled scenarios for testing (e.g., simulating a full storage disk as exemplified by a StubFileSystemService that explicitly returns an error when storage limits are exceeded).

Ensuring Faithfulness in Mocks

  • Mocks should behave as closely to real interactions as possible to avoid false positives/negatives in testing.

  • Emphasizes the need for maintenance and consistency with real-world object behavior.

Reflections on Mock Design
  • Stresses creating thoughtful, flexible test environments by utilizing effective mocking strategies, thereby preventing flaky tests.

  • All classes and fakes associated with testing should also undergo consistent unit testing to ensure quality.

Conclusion on Mocking
When to Use Mocking
  • Use mocking strategically in scenarios involving:

    • Interactions with real-world objects (e.g., emails, payment processing).

    • Potentially slow operations (e.g., database access) that should not occur in unit tests.

    • Non-deterministic behavior that could produce unreliable test results.

  • The general principle is to prefer real implementations where practical while employing fakes or mocks selectively to isolate unit tests effectively.

Domain Modeling Overview
Importance of Domain Modeling
  • Domain modeling is crucial for managing complexity in software development.

  • It involves understanding the problem domain to inform the design and structure of the software.

    • Key Quote: "The essential intricacy of the problem domain itself controls the complexity of development."

Initial Steps for Domain Modeling

Creating a Domain Model

  • Begin by extracting nouns (candidates for classes) and verbs (candidates for methods) from domain descriptions.

  • The example involves developing a model for a Zoo software system:

    • Classes and attributes are derived from the description, forming initial drafts of software requirements.

Engagement with Domain Experts
  • Importance of establishing a ubiquitous language between developers and domain experts to enhance communication and clarity in design decisions.

  • Aim for models that facilitate understanding and reduce ambiguity when discussing requirements and functionalities.

Advanced Domain Design Techniques
Programming with Intent
  • Focus on designing code structures that reflect domain logic clearly, thereby simplifying future adjustments.

  • Use naming conventions that explicitly describe functionality to avoid misinterpretation and improve readability.

Using Type Systems
  • Employ type systems effectively to eliminate invalid state representations within the software.

  • An enum type example illustrates how using descriptive enumerations can prevent invalid inputs in function calls.

    • Example: Instead of accepting a generic string for a status in a function like updateOrder(orderId: string, status: string), define an enum OrderStatus { PENDING, SHIPPED, DELIVERED }. This ensures only valid statuses can be passed, preventing silent failures from typos or incorrect inputs.

Guidelines for Code Maintenance

Performance and Clarity

  • Write code that prioritizes clarity and expressiveness before attempting optimizations; profile the code to identify performance bottlenecks.

  • Encourage constructing tight interfaces with minimal public exposure to maintain a clear architecture that’s easier to edit or refactor over time.

Conclusion
  • The session concludes with a brief recap of key mocking and domain modeling principles, emphasizing the need for clear communication with domain experts and maintaining best practices in software development.