Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Can you put that second sentence into simple english for those imposters such as myself?


They seem to be referring to "duck typing". The typing principle exemplified by the statement: If it looks like a duck, walks like a duck, and quacks like a duck, then it's a duck. Does it matter that it's a waddling man in a duck costume saying "quack"? Nope, still a duck. You don't need an explicit interface and an explicit declaration that you're implementing it, or to implement it fully, you just need to implement the operations relevant to the use of the object.

You have a procedure or something that you want to test and it takes, as a parameter, a logging service? You don't want to instantiate a full logging service and set up the database, because that's heavyweight for a test and irrelevant for the particular test? Fine, you throw together a quick and dirty logger that answers the method `log` and pass that in instead. No need to know what the precise interface has to be, or implement or stub out all its other capabilities. You know it has a `log` method because the procedure under test uses it, so that's what you give your quick and dirty mock logger. No more, no less.


In Typescript or other dynamic languages, here's what you need to do to mock a single method on an instance of a class:

    const instance = new MyClass()
    instance.myMethod = () => getMockResult()
Usually test frameworks provide a helper to do this kind of thing for you, but it's easily accomplished with no magic, using built-in language features.

In nominally-typed languages with static type systems, it's more difficult to alter the behavior of specific instances of classes at runtime. Instead, convention is to declare an interface that specifies some set of methods, and then declare a class that implements the interface. Instead of using a concrete type in business logic, you use the interface type instead. Then, in your tests you can use a different class that also implements the interface:

    interface MyInterface {
      myMethod(): ResultType
    }
    
    class MyClass implements MyInterface {
      myMethod() {
        return new ResultType()
      }
    }
    
    class MyInterfaceMock implements MyInterface {
      private myMethodImpl: () => ResultType
      constructor(myMethodImpl: () => ResultType) {
        this.myMethodImpl = myMethodImpl
      }

      myMethod() {
        return this.myMethodImpl()
      }
    }

    const instance: MyInterface = new MyInterfaceMock(() => getMockResult())
The "duality" I'm referring to is that in this pattern, every type needs to be defined twice, first as an interface, and then as an implementation.

As other commenters said, there are tools like Mockito that eliminate the need for this double-definition, and enable the same easy re-definition of method behavior at runtime. My point wasn't that is is impossible in Java or other static languages, just that one of the strengths of a more dynamic language is that flexible runtime behavior alteration is ~2 lines of code, versus the 39769 lines of code in eg Java's Mockito.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: