Testing Public Interface

181 views
Skip to first unread message

Dariusz Gafka

unread,
Oct 26, 2016, 2:03:28 PM10/26/16
to Growing Object-Oriented Software
Hello,

I am strugling with one problem about testing public interface only.

I think the best explanation will be, if I start with example. So here we go :)

We are having

public class TransactionService {
    private TransactionProviderFactory transactionProviderFactory; 
    TransactionService(TransactionProviderFactory transactionProviderFactory) {
        this.transactionProviderFactory = transactionProviderFactory;
    } 
    public void transfer(Money money, String type)
    {
         transactionProvider = this.transactionProviderFactory.getFor(type);
         transactionProvider.startTransaction(money);
    }
}
class TransactionProviderFactory {
    
   TransactionProvider getFor(String type) {
     //returns for type
  }
}

Now let's say they both belongs to the same package. 

1. I should test only TransactionService as it's the only API, that is exposed in this package. Other packages should only talk to TransactionService, not other class in this package.
 But now constructing TransactionService for tests requires TransactionProviderFactory, which I shouldn't know about, because it's internal to the package.
 How should I handle it?
 Should I mock it or create real instance and inject or maybe do something else with it? 

2. The TransactionProviderFactory contains logic also. 
 It should be tested against returning correct Provider for specific type.
 It should be tested for throwing exception when retrieving not existing TransactionProvider.
Should I write the above tests for TransactionService? 

Israel Fonseca

unread,
Oct 26, 2016, 2:26:29 PM10/26/16
to Growing Object-Oriented Software
1.a I would create a stub of TransactionProviderFactory that given a certain type return a mock X, and verifying that the mock X calls the startTransaction with the input money.
1.b I would reconsider the design of having the factory as the collaborator of the TransactionService, and probably just having an TransactionProvider as the only dependency. In that scenario the transfer() method wouldn't receive the type also (I don't know if this is possible).

2. Don't redo those tests in the TransactionServiceTest. If you had to handle the exception in the TransactionService, in that case you should test this behaviour.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Dariusz Gafka

unread,
Oct 26, 2016, 2:59:27 PM10/26/16
to Growing Object-Oriented Software
Thanks for your fast answer.

1b Well this is not possible. I can decide which transaction provider I use only at the run time.

1a
If I am supposted to stub the TransactionProviderFactory my tests will depend on existence of it. For example I will want to move the behaviour of deciding which provider to choose somewhere else. I will have to not only change creating of the Service, but also every test, related to transfer method. Isn't there best way of handling it? Because my feeling goes for just passing the real instance to the constructor, but from other side it looks really bad to pass instance of stateless service instead like you mentioned stubbing it.

2.
Well if I am supposted to test for exceptions from TransactionService and have stub of TransactionProviderFactory, I will have to stub throwing exception and also write tests for TransactionProviderFactory to proof, that it for sure throws exception, when there is no specific type
 
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.

Dariusz Gafka

unread,
Oct 26, 2016, 3:13:38 PM10/26/16
to Growing Object-Oriented Software
Also I am curious what test cases could be used for minesweeper kata.

So there would be a Game class, that accepts some board (array of arrays) and returns new board with marked numbers.
Other classes would be internal ones and shouldn't be tested in my understanding of doing TDD well.
But in this way the unit tests are becoming pretty large and are the same as acceptance tests(!) for this game, which I guess is not correct

Matteo Vaccari

unread,
Oct 26, 2016, 3:40:23 PM10/26/16
to growing-object-o...@googlegroups.com
Hi Dariusz,

this example code does not seem to do much, now, does it?  Perhaps the only interesting thing that happens here is in the getFor() method, which you didn't show.  This looks to me like you came up with a solution, and you're now trying to write tests that prove that your solution exists.  Good tests (IMO) are tests that talk about specific cases of business things that are interesting to your customer.

A better approach (in my opinion) is to start with a blank slate: forget about the TransactionProvider and the TransactionProviderFactory altogether.  Write a test that 

How about you start with a test of what interesting things happen, from the point of view of the business, when someone calls

  transfer(new Money(1), "whatever")

Try to solve it in the simplest possible way, then proceed with the next variation of

  transfer(X, Y)

that makes sense to the business.  Don't write more code than is required to pass the tests.  Let the tests guide you.

Hope this helps,

Matteo




--

Israel Fonseca

unread,
Oct 26, 2016, 5:29:10 PM10/26/16
to growing-object-o...@googlegroups.com
Round 2 :)

1.a Maybe I didn't get it right, but if you use a real instance it won't solve the problem of having to change all the tests in the case that you figure out that you don't need the factory anymore. When you say: "my tests will depend on existence of it" as a bad thing, I don't agree. You are testing a class that depends on another collaborators and makes sense to the test express this necessity somehow (you have to give a real/fake/stub/mock implementation to it). By having to handle this dependency in your test, the test is also giving some feedback about your design (when you happen to have 5+ collaborators you will think "man, there's something wrong in here").

2. In that case, yes, I would create a test to check if my TransactionService handles the failure accordingly (write some log file), and for that I would stub the collaborator to throw an exception and check the logging (and yes, It'll be like a repeated test, but one case if for identifying an error and the other is about how to handle the error).

I recommend the following video, could give you some insights about testing:


To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.

Dariusz Gafka

unread,
Oct 27, 2016, 2:19:31 AM10/27/16
to Growing Object-Oriented Software
@Israel 
I understand your point of view, because I always do the same. But I just feel there is way to improve it and I am trying to find that way. :)
Doing TDD by the book as far as I understand would be in this way:
Red -> write the failing test
Green -> make it pass
Refactor -> On this stage I should be able to refactor the code without changing the tests. Let's say I want to get rid off TransactionProviderFactory and implement it differently.  If I stub it I will have to change the tests, which shouldn't be changed. 

@Matteo
After some time of thinking, you're right in here. I came with the solution first even that I wrote the tests first. I know, that is not way to do it, but the experience and habits are just strong. When I start to thinking of some problem, my brain tries to find solution immediately. 
So "Let the tests guide you." isn't so simple, like it sounds. :)
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.

Matteo Vaccari

unread,
Oct 27, 2016, 2:28:58 AM10/27/16
to growing-object-o...@googlegroups.com

@Matteo
After some time of thinking, you're right in here. I came with the solution first even that I wrote the tests first. I know, that is not way to do it, but the experience and habits are just strong. When I start to thinking of some problem, my brain tries to find solution immediately. 
So "Let the tests guide you." isn't so simple, like it sounds. :)


You are right, "Let the tests guide you" is not easy! You may at first treat it like a game with simple rules: don't write more code than is needed to pass hte test. Focus on the process, not on your deadline. It may be easier to do that on a kata rather than on real work :)

The video training by JB Rainsberger is quite good: http://online-training.jbrains.ca/p/wbitdd-01

Matteo

Israel Fonseca

unread,
Oct 27, 2016, 4:44:37 AM10/27/16
to growing-object-o...@googlegroups.com
@Darius
Now I got your point. From my experience: If you choose the path of Mocking/Interaction testing, you can't avoid this kind of coupling with the test implementation, In this case you may use this over-constraining as an advantage to check if your test is being to hard to build (in that case your SUT may be to big). This design upfront thing that @Matteo warned you about, indeed can be a dangerous thing, but it could no too (vague answer. :P). JBrains and Sandro mancuso talk about this in here: https://www.youtube.com/watch?v=ty3p5VDcoOI

To have the advantage to really be able to refactor freely (besides changes in the constructor), only with the Classist Approach of using the real implementation on an alternate in-memory implementation (like Uncle Bob does). But this path come with it's own 'problems': fixtures that can be are hard to setup, maintaining another implementation of your collaborators (like an ProductRepositoryInMemory) and being a harder to implement you app in a top-bottom fashion.

I spent lot of time doing both and in the end I think it's matter of taste, you have to choose the technique that fits your 'way of programming' better. @Matteo and @Darius what do you think about it?


To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.

Matteo Vaccari

unread,
Oct 27, 2016, 5:56:38 AM10/27/16
to growing-object-o...@googlegroups.com
Hi Israel,

IMO it's not a matter of "choosing a path" between using mocks or not using them.  They are one tool out of several.   

Usually I start with the business requirements, and list the tests that I think are needed to meet those requirements.  Then I write the first test and start the TDD process.  In the course of doing this, I write more tests, and sometimes I find that some tests are too hard to write, or too hard to pass.  Then I have to think and probably change the design.  The style of test that I use depends on the context.  I always try to make the test tell a story about what I'm trying to do.  Sometimes mocks help me do that, sometimes I use other techniques.  

When mocks get in the way of refactoring, it usually happens because I'm mocking an interface that is not a good abstraction for the domain.  You should not expect to find the right abstractions easily!  It generally takes more than one try.  Once you have found a good abstraction, it tends to be stable.  It may help to live with duplication for some time before you start abstracting.

Hope this helps!

Matteo








To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.

Dariusz Gafka

unread,
Oct 27, 2016, 6:57:52 AM10/27/16
to Growing Object-Oriented Software
@Israel
Is Uncle Bob really using in memory implementations for unit testing? 
This is really weird thing to me, because in my opinion unit tests are not place for in memory implementations. I use them for acceptance tests only written in gherkin.

@Matteo

"When mocks get in the way of refactoring, it usually happens because I'm mocking an interface that is not a good abstraction for the domain"
Do you mean, that when we find correct abstraction for the domain, we will no need to change interaction between two objects while refactoring?
What about situation when the abstraction is correct, but we would like to delegate it to some other place (it will break the tests)? 


I would love to see some of your code examples with tests, to better understand your way of thinking. 

Israel Fonseca

unread,
Oct 27, 2016, 7:18:29 AM10/27/16
to Growing Object-Oriented Software
@Matteo
I agree with that, these techniques are tools and we have to be able to choose the right tool for the task in hand. My phrasing didn't get right. :)

@Darius
I'm pretty sure that I saw this approach in this series: https://cleancoders.com/videos/java-case-study. And besides that, that's the main point of his famous story about how the Fitnesse framework started with an in-memory database for testing and in the end they figured that they didn't need a 'real' database at all. Extra advice: if you have time search for 'Functional Core, Imperative Shell' as another strategy to design/test your application (I'm studying about it now).


To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.

Matteo Vaccari

unread,
Oct 27, 2016, 7:39:45 AM10/27/16
to growing-object-o...@googlegroups.com
"When mocks get in the way of refactoring, it usually happens because I'm mocking an interface that is not a good abstraction for the domain"
Do you mean, that when we find correct abstraction for the domain, we will no need to change interaction between two objects while refactoring?
What about situation when the abstraction is correct, but we would like to delegate it to some other place (it will break the tests)? 

Nothing ever is perfect; you will always have some breakage when things evolve. If the breakage is massive, then you probably have too much duplication.


I would love to see some of your code examples with tests, to better understand your way of thinking. 

This is a sequence of exercises that I wrote to specifically show how to keep technical and boilerplate code to the minimum when doing a web application:


One of my friends is using them in boot-camp training for new hires.  I'm not sure it will answer your questions, but at least it shows which questions are important to me :)

Matteo



To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsu...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsu...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsu...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsu...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsu...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Dariusz Gafka

unread,
Oct 28, 2016, 12:56:32 AM10/28/16
to Growing Object-Oriented Software
Seems, that there is no golden rule here (And I wish there would be one :)).
I will have to spend some time on testing both of ways. So I can find the proper usage context for each one. 

Anyways thanks for all your answers :)

Dariusz Gafka

unread,
Oct 28, 2016, 3:33:30 AM10/28/16
to Growing Object-Oriented Software
Seems, that the thing I was looking for is "Detroid school of TDD".

"Every test written in a Detroit-school test suite is designed to maximize regression safety. As a result, testing the subject under sufficiently realistic conditions is considered paramount to maximize the resulting tests' regression value. Therefore, use of test doubles is seen as an affordance to be minimized, often by reworking the broader design to obviate them.

This introduces a surprisingly complex responsibility of Detroit-school TDD practitioners: to define what level of realism is acceptable for a test to be called a "unit test". Rules similar to the ones published by Michael Feathers in 2005 are common and often debated within teams as suites grow and become slower.

By using test doubles as a convenience to work around slow dependencies or too-difficult-to-test situations (e.g. verifying a side-effect occurred), the unit tests in a Detroit-school suite tend to be heavily incidentally dependent on the behavior of the subject's dependencies. This acts as a double-edged sword: on one hand, when the behavior of a dependency changes in an unanticipated way, tests of its users will helpfully fail; on the other hand, large test suites will exhibit the problems caused by highly redundant coverage.

In contrast, London-school practitioners tend to have very clearly defined rules for when to use mocks. Because very few people in the broader public understand that there are two camps—much less the nuanced differences between them—a developer brought up in the Detroit-school will often look at a London-school unit test suite and immediately draw the conclusion that it has succumbed to extreme over-mocking."


"A common pattern to emerge when practicing Detroit-school TDD is that an author will write some number of examples against a single public API, which in turn necessitates the creation of numerous private methods, which then leads to relatively large and unwieldy units. This places all the design pressure on the author to answer for themselves questions like:
  • "I have a large unit, what proper Design Patterns™ can I refactor this into?" (Blank-slate syndrome)
  • "How many examples are enough to test the unit at this level of granularity?"
  • "At what point are my implementation's private methods sufficiently complex that they warrant the creation of a separately-tested public API?"
  • "What degree of redundant test coverage between the original public API and any subsequently-extracted public APIs is acceptable? Once that threshold is crossed, should redundant examples be culled or should the newly-extracted API be replaced by a test double in the original test?"

Above comes from https://github.com/testdouble/contributing-tests/wiki/Detroit-school-TDD

Nat Pryce

unread,
Oct 28, 2016, 8:17:09 AM10/28/16
to growing-object-o...@googlegroups.com


On 28 October 2016 at 08:33, Dariusz Gafka <darius...@gmail.com> wrote:

 when the behavior of a dependency changes in an unanticipated way, tests of its users will helpfully fail...


I don't find the distinction between London/Detroit or Mockist/Classical useful.  I've written about that before on this list. But this sentence highlights why:

Mocks are a tool for testing objects that tell other objects either (a) "Make something happen!", or (b) "Something has happened, and you might need to react in some way!".  The object should not care what behaviour results or even have visibility of it.  If it does, a mock library is the wrong tool.  In that case, I'd either use the same implementation of the dependency that is used in the deployed system, or write an in-memory implementation of the protocol between object and dependency, and use contract tests to ensure that it behaves the same as the implementation used in the deployed system.

--Nat



--

Israel Fonseca

unread,
Oct 28, 2016, 11:05:42 AM10/28/16
to growing-object-o...@googlegroups.com
Nat, so are you in favor of using an InMemory implementation of an Repository if the objective is to control the indirect inputs of the SUT? Because if you stub a query method with a Mock Framework and that real implementation changes later, you won't get the "when the behavior of a dependency changes in an unanticipated way tests of its users will helpfully fail [...]" benefit.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.

Nat Pryce

unread,
Oct 28, 2016, 12:47:15 PM10/28/16
to growing-object-o...@googlegroups.com
On 28 October 2016 at 16:05, Israel Fonseca <israe...@gmail.com> wrote:
Nat, so are you in favor of using an InMemory implementation of an Repository if the objective is to control the indirect inputs of the SUT? Because if you stub a query method with a Mock Framework and that real implementation changes later, you won't get the "when the behavior of a dependency changes in an unanticipated way tests of its users will helpfully fail [...]" benefit.

Yes.  If it's simple lookups, then it's easy to implement with a map.  If it's a more complicated interface with a mix of queries and commands that have interrelated behaviour, then a contract test is even more important.

--Nat

 

Em sex, 28 de out de 2016 às 10:17, Nat Pryce <nat....@gmail.com> escreveu:


On 28 October 2016 at 08:33, Dariusz Gafka <darius...@gmail.com> wrote:

 when the behavior of a dependency changes in an unanticipated way, tests of its users will helpfully fail...


I don't find the distinction between London/Detroit or Mockist/Classical useful.  I've written about that before on this list. But this sentence highlights why:

Mocks are a tool for testing objects that tell other objects either (a) "Make something happen!", or (b) "Something has happened, and you might need to react in some way!".  The object should not care what behaviour results or even have visibility of it.  If it does, a mock library is the wrong tool.  In that case, I'd either use the same implementation of the dependency that is used in the deployed system, or write an in-memory implementation of the protocol between object and dependency, and use contract tests to ensure that it behaves the same as the implementation used in the deployed system.

--Nat


--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Israel Fonseca

unread,
Oct 28, 2016, 3:19:52 PM10/28/16
to growing-object-o...@googlegroups.com
Interesting, you use this a golden rule and always create contract tests for your in-memory implementations or just for those complicated interfaces? I feel a little bad (or just lazy) with the idea of creating contract test for all my InMemory implementations.

An finally, so you would mix InMemoryImplementations and mocks in your unit tests. For example to test the following code (very dumb example):

class CheckUserPermissionUsecase{

  constructor(userGateway, output)
    this.userGateway = userGateway
    this.output = output
  
  execute(name){
    user = this.userGateway.find(name)
    if(user.age > 18)
      this.output.display('Allow')
    else{
      user.incrementNumberOfFailedAttempts()
      this.userGateway.update(user)
      this.output.display('Do not allow')
    }
  }
}

You would mock the output and assert that the display method sent the right message, and for the increment in the user you would query the InMemoryDatabase and check for the changed value. That's right?

I missed this kind of examples in the GOOS book, so it's interesting to know your opinion. :)


Em sex, 28 de out de 2016 às 14:47, Nat Pryce <nat....@gmail.com> escreveu:
On 28 October 2016 at 16:05, Israel Fonseca <israe...@gmail.com> wrote:
Nat, so are you in favor of using an InMemory implementation of an Repository if the objective is to control the indirect inputs of the SUT? Because if you stub a query method with a Mock Framework and that real implementation changes later, you won't get the "when the behavior of a dependency changes in an unanticipated way tests of its users will helpfully fail [...]" benefit.

Yes.  If it's simple lookups, then it's easy to implement with a map.  If it's a more complicated interface with a mix of queries and commands that have interrelated behaviour, then a contract test is even more important.

--Nat
Em sex, 28 de out de 2016 às 10:17, Nat Pryce <nat....@gmail.com> escreveu:


On 28 October 2016 at 08:33, Dariusz Gafka <darius...@gmail.com> wrote:

 when the behavior of a dependency changes in an unanticipated way, tests of its users will helpfully fail...


I don't find the distinction between London/Detroit or Mockist/Classical useful.  I've written about that before on this list. But this sentence highlights why:

Mocks are a tool for testing objects that tell other objects either (a) "Make something happen!", or (b) "Something has happened, and you might need to react in some way!".  The object should not care what behaviour results or even have visibility of it.  If it does, a mock library is the wrong tool.  In that case, I'd either use the same implementation of the dependency that is used in the deployed system, or write an in-memory implementation of the protocol between object and dependency, and use contract tests to ensure that it behaves the same as the implementation used in the deployed system.

--Nat


--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriente...@googlegroups.com.

Dariusz Gafka

unread,
Oct 29, 2016, 3:05:46 AM10/29/16
to Growing Object-Oriented Software
Nat,
If the Israel's example is true, I understand you mock every object that doesn't return values, and you create in memory implementation or use real ones for query objects. 
If that's true, do you write acceptance tests? 
And where do you set state for UserGateway, in some setup for your test case? 


This is something really interesting. Using in memory in unit tests truly decouples the test cases from implementation.
And why not to go any forward and create dumb implementations instead of using mocks? :) (write methods)
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.

Grzegorz Gałęzowski

unread,
Oct 29, 2016, 5:22:03 AM10/29/16
to Growing Object-Oriented Software
Nat,

If that is the case, then what about the jmock's syntax such as allowing (turtle).queryPen(); will(returnValue(PEN_DOWN));? Do you not find it useful anymore? And what about the "allow queries, expect commands" advice?

Regards,
grzesiek

Nat Pryce

unread,
Oct 31, 2016, 5:52:54 AM10/31/16
to growing-object-o...@googlegroups.com
On 28 October 2016 at 20:19, Israel Fonseca <israe...@gmail.com> wrote:
Interesting, you use this a golden rule and always create contract tests for your in-memory implementations or just for those complicated interfaces? I feel a little bad (or just lazy) with the idea of creating contract test for all my InMemory implementations.

As a rule of thumb, I'll end up with contract tests by refactoring.  I don't write them *for* in-memory implementations.  I'll write a test, and then pull out the commonalities into a contract test that can be applied to different implementations, the in-memory implementation being one of them.

 
An finally, so you would mix InMemoryImplementations and mocks in your unit tests.

Sometimes.  I do find a mock library useful for testing event emission and outgoing commands in a tell-don't-ask style.
 
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--

---
You received this message because you are subscribed to the Google Groups "Growing Object-Oriented Software" group.
To unsubscribe from this group and stop receiving emails from it, send an email to growing-object-oriented-software+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Nat Pryce

unread,
Oct 31, 2016, 6:18:48 AM10/31/16
to growing-object-o...@googlegroups.com
In that example, the CheckUserPermissionsUsecase class seems to have mixed responsibilities. It has to do three things -- find a user, check age restriction, report whether a user is allowed or not.  But it reports that the user is not allowed to two different objects -- the display and the user -- and must persist users correctly in the user gateway, otherwise (I guess) the app won't work.  That makes me think that it has too much knowledge of how the rest of the application is structured.  The interfaces it depends on are not defined in *its* terms, but in terms of the rest of the application's implementation details. I'd want to push that knowledge out, so that its implementation is only concerned with its main responsibility, which appears to be enforcing age restrictions. So I'd give it a way to look up users, and a way to report the result of its check as an event.  Something like:

interface UserLookup {
    fun findByName(name: String): User
}

interface PermissionDecision {
    fun rejected(user: User)
    fun allowed(user: User)
}

and then the check could be implemented as:

class UserAgeRestriction(val users: UserLookup, val decision: PermissionDecision) {
    fun execute(name: String) {
        val user = users.findByName(name)
        if (user.age > 18) {
            decision.rejected(user)
        } else {
            decision.allowed(user)
        }
}

An implementation of PermissionDecision would display the decision to the display and increment a counter for the user, and whatever policies are needed by the rest of the application.

Now, looking up the user and reporting the events are very simple and could well be tested with a mock object library.  But the user lookup could be just as easily be implemented with a map in unit tests.

--Nat

Nat Pryce

unread,
Oct 31, 2016, 6:27:46 AM10/31/16
to growing-object-o...@googlegroups.com
On 29 October 2016 at 10:22, Grzegorz Gałęzowski <grzesiek....@gmail.com> wrote:
Nat,

If that is the case, then what about the jmock's syntax such as allowing (turtle).queryPen(); will(returnValue(PEN_DOWN));? Do you not find it useful anymore? And what about the "allow queries, expect commands" advice?

Partly experience -- we've learned how to use the tools more effectively over the years.  Partly language -- I'm now writing almost exclusively in Kotlin, not Java, and it's much easier to write one-off implementations of an interface for testing in Kotlin than in Java.

In Java I would probably use the `allowing` syntax with very straightforward query interfaces, like factories and the like, because it is still much less verbose than writing an object.  But as soon as the object's interface became more complicated I'd switch to an in-memory implementation.

I still follow the rule of thumb of "allow queries, expect commands", for the same reasons, whether using a mock object library or not.

Cheers,
    --Nat
Reply all
Reply to author
Forward
0 new messages