Integration Testing with Spring – Mocking I

Gil Zilberfeld explains how to using mocking in Spring integration tests
Standard
In this series we're taking at Spring and its features supporting testing in general, and specifically integration tests.
Dependency injectionConfigurationsNested configurationsOrganizing configurations
Primary beansAvoiding problemsProfilesMocking I - Lifecycle
Mocking II - ResetMocking III - MockBeanData I - @SQLData II - JDBC

Data III - JPA
Controllers IControllers IIConsumer tests

Spring has some cool features, and Spring Boot doesn’t just pack JUnit into its packages, but also Mockito, so we can use mocking to our hearts content.

We just need to be careful in our integration tests. We need to be wary of…

The Bean Life-cycle

Remember the whole dependency injection power of Spring? So far, we’ve used the phrase “when the application loads”, to say when beans are injected. And that is mostly true (unless you programmatically refresh the beans, more on that later).

“When the application loads”, the application context loads, and with it the whole set of Configurations, which include beans. For example:

So far, so good. We’re injecting a mock. Now let’s look at our completely contrived test class that uses that bean.

Our tests check if the publish method was called. When we run it, the firstTest passes, and the secondTest fails. But why?

Our mockPublisher instance, used in both integration tests, is injected only once, when the test class instance is created. That’s the default injection mode in Spring, called “singleton” scope.

Using singletons is great in many use cases. However, we don’t want this behavior for mocking. We want mocks to be created every time from scratch, to lower the dependency between tests. In unit tests, we have full control on how to create any instance, so just recreating them in the setup or the test method is enough. With Spring integration tests it’s different.

So what can we do?

Using a prototype

When using “singleton” scope, the factory method is called once, and the instance is cached. Using “prototype” scope tells Spring that every time we need an instance, it should create a new instance, like we want from our mock.

Alas, in our integration tests, we have a single creation (or injection) for our mock. The factory method in the MockPublisherConfiguration is called only once.

But that’s because we’re using @Autowired. When the test class loads, Spring sees the @Autowired member and injects it, and that happens a only once. What if we get the bean programmatically, rather than through @Autowired?

Let’s change our test class a bit:

As you can see, the mockPublisher is no longer @Autowired. We’re injecting the Spring ApplicationContext, and use it before every test. The getBean method fetches the mockPublisher instance for every test. This alone doesn’t do the trick, though. Remember, once the factory method is called, the instance is cached. Now, let’s change the Configuration class:

We’ve added the “prototype” scope to our bean and presto – Both integration tests pass!

Now we get a different instance for every test. The second one’s publish method indeed does not get called.

It works! We’ve got a different mock for each test.

Here’s the bad news: Most people don’t even understand how Spring works, so getting hot and heavy with the ApplicationContext may be too much for them. Not you, of course, but other people.

So for them, next time we’ll look at other options for mocking in Spring integration tests.

Integration Testing with Spring – Profiles

Gil Zilberfeld explains Spring files and how they relate to integration tests
Standard
In this series we're taking at Spring and its features supporting testing in general, and specifically integration tests.
Dependency injectionConfigurationsNested configurationsOrganizing configurations
Primary beansAvoiding problemsProfilesMocking I - Lifecycle
Mocking II - ResetMocking III - MockBeanData I - @SQLData II - JDBC

Data III - JPA
Controllers IControllers IIConsumer tests

Today we’re going to discuss Spring profiles. Profiles are sort of a “meta configuration” in Spring. They are like a configuration for a configuration.

We usually set profiles up with either a role (“dev”, “tester”, “devops”, etc.) or with an environment (“dev”, “test”, “integration”, etc.). They are “meta” in the sense that there’s a bunch of configurations that are related to developers, and then there are a different subset for testers, for example. Those bean groups can be combined, or intersect, based on the needed configuration. We can set profiles on a configuration class or a single bean, and basically tell Spring whether to load them or not, based on the active configuration.

Let’s say we have this configuration class in production:

As you’ve probably guessed, it creates a real Logger. Now, in our integration test class we can use (as we saw in the @Primary post) a different configuration.

When we use the TestConfiguration it will (normally) inject a FaultyLogger. But since we’ve put a @Profile on it, the beans in the TestConfiguration will be loaded only if the active profile when the tests run is Hacker.

The test class might look like that:

The @ActiveProfiles annotation tells the integration test class under which profile it runs (obviously). So everything works fine.

If we remove the @ActiveProfiles annotation, we’ll get a NoSuchBeanDefinition exception. That is because the “hacker” profile was not activated, and therefore the TestConfiguration was not loaded. And since the test does not import the AppConfiguration directly, it doesn’t find a Logger bean.

If we would import the AppConfiguration, or the TestConfiguration but without the @ActiveProfile set to hacker, the test will fail, because it will inject the normal logger.

By now, you’re probably asking: so what?

Profile: What is it good for?

In integration testing? Not much.

Like many features in Spring, they are useful in their context. But for integration testing, we’re looking for something else.
We want tests that do not fail or pass on their context. An active profile is such a context. So we want to limit the impact of something like that on our tests.

Let’s say the application we’re testing has some hacker-context related features we want to test. It’s written using Spring’s Profile features, and so these features must be run under an active Hacker profile. That’s cool.

How would we go about testing that? We’d create a test class (or two) that use the @ActiveProfiles annotation, like we did in our example. In there we’ll put all the Hacker profile related tests. By putting all these tests together in one (or two) places, we’ve already separated them from the rest of their tests. Which helps the deterministic aspect we’re looking for.

Come to think about it, that’s how we usually separate test classes from each other, right? based on features or context?

Using Profiles doesn’t really help us in testing, it just supports Spring app code that’s written with Profile support. Which is again, cool.

There’s a minor scenario where using @ActiveProfiles with different profiles might be helpful: If the integration tests are already mashed up together without profile separation. If something goes wrong, you might want to play with @ActiveProfiles to switch profiles and debug what’s happening.

However, if you’re doing that, you might want spend more time on separating the tests into different test classes which run under different profiles.

Integration Testing With Spring – Avoiding Problems

Gil Zilberfeld describes how to avoid problems created with bean injection in Spring integration tests
Standard

In this series we're taking at Spring and its features supporting testing in general, and specifically integration tests.
Dependency injectionConfigurationsNested configurationsOrganizing configurations
Primary beansAvoiding problemsProfilesMocking I - Lifecycle
Mocking II - ResetMocking III - MockBeanData I - @SQLData II - JDBC

Data III - JPA
Controllers IControllers IIConsumer tests

We already know that Spring is complex. And we’ve just scratched the surface. One of the problem with complex frameworks is that they do so much, that when something goes wrong they are not that smart about analyzing the situation. And that really gets in our way of doing actual programming.

For example, when a Bean doesn’t load, it can tell you that there was a problem loading it, but what exactly happened is hard to find on the stack. Especially if one dependency called another, and we are five levels deep in bean dependency. You can spend a lot of time here, so here are a couple of tips on how not to get there.

Don’t do anything important in the constructor

Beans usually create objects and return them. Those objects have constructors, that can do many things. But you shouldn’t.

You can store things in variables. But avoid any code in the constructor of your injected object. While we’re at it, definitely don’t have any static initializer that can create some “invisible” damage in the stack model of “who runs first”. If you need to initialize something in the object, add an Initialize method. I know it’s not Object Oriented in clean form, but we’re building components that integrate with a massive system, and it’s hard enough when it works.

Add a verification test in your test class

Remember the contextLoads() test that’s created for us in the Spring Boot test folder? What it does, as the name implies, is verify the ApplicationContext loads correctly. If it doesn’t, there’s not much point in running the rest of tests, right?

This test is like our canary in the coal mine. We can take this idea further.

When you add your own tests, especially when relying on automated wiring that Spring does from explicit configuration code, or god help us, implicitly and automatically. A simple test that checks that:

We’ll get to JdbcTemplate in a future post, but suffice it to say it can be injected automatically to different objects in the system. Having this test in place localizes the error, and makes it easier to investigate what went wrong and fix it.

Whenever you’re starting a new test class, it’s advisable to have something like this around. And don’t throw it away, keep it around for stormy Spring weather. Winter is coming.

Integration Testing with Spring – Primary Beans

Gil Zilberfeld explains the Primary bean annotation for using in integration tests.
Standard

In this series we're taking at Spring and its features supporting testing in general, and specifically integration tests.
Dependency injectionConfigurationsNested configurationsOrganizing configurations
Primary beansAvoiding problemsProfilesMocking I - Lifecycle
Mocking II - ResetMocking III - MockBeanData I - @SQLData II - JDBC

Data III - JPA
Controllers IControllers IIConsumer tests

Last time we talked about breaking down configurations into small parts, so we can work in smaller chunks, that make our integration tests easier. This leads us to the next subject.

Remember that question we had? How does Spring know which bean to inject if it has multiple factory method for the same type? Let’s answer this in a single configuration class.

Let’s say in our main configuration class there are two beans of type Printer. We already talked about how to select a bean using its name. But Spring gives us a way to specify a default bean: @Primary.

When Spring sees a class with:

It will inject the DotMatrixPrinter one, because that bean has the @Primary annotation.

How can we use @Primary in integration tests?

@Primary becomes very effective in tests, because we might want to override the bean that is already defined in production. Let’s say our Printer type is configured in the app configuration like this:

So Printer will always result in injecting a LaserPrinter.  However, in our test we want to inject a mock Printer.

So lets create a test configuration:

Now, remember that when we import a configuration, there’s no “override” operation between the configurations, it’s adding the beans from the imported configuration to the importing one.

What happens when we use the PrinterTestConfiguration? The bean mockPrinter we added with @Primary tells Spring to load itself “over” the laserPrinter production one. The only trick is to not specify a @Primary in the main configuration.

Integration Testing With Spring – Organizing Configurations

Gil Zilberfeld explains how to organize Spring configurations for integration testing
Standard

In this series we're taking at Spring and its features supporting testing in general, and specifically integration tests.
Dependency injectionConfigurationsNested configurationsOrganizing configurations
Primary beansAvoiding problemsProfilesMocking I - Lifecycle
Mocking II - ResetMocking III - MockBeanData I - @SQLData II - JDBC

Data III - JPA
Controllers IControllers IIConsumer tests

We’ve talked about nested configurations. Now, let’s move on to using imported configurations in a smart way.

Let’s say in the main app (as in “production”), I have a whole lot of beans I need to inject.

The thing not to do (although you might be tempted) is dump them all into one big configuration class. It might feel like “just one more bean wouldn’t hurt”. But it will. Maybe not today, maybe not tomorrow. But soon, and for the rest of your project life.

We might end up with tens, even hundreds of beans, so using a big class may be convenient in answering “where do I put my next shiny new bean”, but at the same time, we probably want to segment them better .

Separate Configurations

One way to do this is by service or functionality: Each service has its own configuration. With micro-services this is crucial. When we update the service, we update the configuration too, and deploy them as a single unit.

There are some things that are more cross-cutting, and less service related, like general database connectivity initialization beans. We can put those in a separate configuration class. Then for the application we can use @ContextConfiguration to load the right configuration classes:

As alternative, we can also  @Import at the configuration class level:

And then at the service class:

While both of these work, we conceal the fact that we’re using the DatabaseConfiguration in the service. This can come back to bite us later, as we’re carrying invisible baggage. So let’s stick with the first one.

And Testing Configurations?

When breaking down a full configuration into separate parts, we get a couple of advantages for testing:

  • We load only what we need for the test, not the entire system configuration
  • Run time (including load time) can seriously decrease
  • Less things that can fail our tests
  • Test setup becomes less of a hassle.

Here’s an alternative to the nested configuration example, only this time, the configuration is in a separate class.

In this case, the MainControllerConfiguration class is in the production part of the application, and the ControllerTestConfiguration is in the test part (obviously). The latter imports the former, which can save us time in creating objects we already have defined, and don’t need to change.

So when we use the ControllerTestConfiguration in a test it looks like this:

Notice that this way, we keep the link to the configuration through our ControllerTestConfiguration importer. Only it decides what to import or override. The test class is oblivious. This is quite cool.

So what’s “Overriding” a bean? That’s next.

Integration Testing with Spring – Nested Configurations

Gil Zilberfeld discusses spring features for integration testing, this time nested configurations
Standard

In this series we're taking at Spring and its features supporting testing in general, and specifically integration tests.
Dependency injectionConfigurationsNested configurationsOrganizing configurations
Primary beansAvoiding problemsProfilesMocking I - Lifecycle
Mocking II - ResetMocking III - MockBeanData I - @SQLData II - JDBC

Data III - JPA
Controllers IControllers IIConsumer tests

Let’s dig deeper into configurations for integration tests in Spring.

We’ll start with a nested integration context example, for a class (that might be a test class, because that’s our main focus). While we can use @Import, if we set up one-time configuration, it could be nested in the test class. Here’s an example:

Let’s take a look at what we have here. Our test class, and code-under-test will inject two @Autowired objects. The first one is the controller. Its @Bean is configured in another configuration class, called ControllerConfiguration. This configuration is imported by the nested configuration in the test class.

The NestedConfiguration class declares a bean that creates and initializes a mock, which is probably a different implementation from the main app implementation. Also, the reason for using a one-time configuration is having the initialization different than other configurations, used by other test classes.

We need to remember a couple of things of how Spring works:

  • The nested configuration class has to be static. While usually not a problem, it loads differently than regular configuration classes.
  • If we use an @ContextConfiguration on the test class (not @Import on the nested configuration class, as in the example), it will override the nested configuration. That can be confusing and can cause hilarious (or disastrous) consequences.

The cool thing about a nested configuration is that it’s right there in the test class that needs it, so no need to look for it elsewhere. That’s a readability plus.

However, nested configurations are not re-usable, and we would probably want to reuse a single configuration for multiple test classes. So, unless nested configurations are your default way of working, it’s probably better to use non-nested ones. They should be used in very special circumstances for very special initialization.

Next, we’ll look at managing and organizing configurations for production and tests.

Integration Testing With Spring – Configurations

Gil Zilberfeld explains about Spring features for testing and integration testing, this time with configurations
Standard

In this series we're taking at Spring and its features supporting testing in general, and specifically integration tests.
Dependency injectionConfigurationsNested configurationsOrganizing configurations
Primary beansAvoiding problemsProfilesMocking I - Lifecycle
Mocking II - ResetMocking III - MockBeanData I - @SQLData II - JDBC

Data III - JPA
Controllers IControllers IIConsumer tests

So how does Spring know what type to inject?

Spring can do so in a different ways. The default is scanning any class in the class path. Spring tries to match the types it finds in the class path to those needed by @autowired declarations, and if there’s a match, it will inject it.

However, this solution is limited. It doesn’t solve the interface problem – if there are multiple implementors, it still won’t have enough information to inject the right type. Also, what if we need to run it in different way? For example, in one test we need a LaserPrinter and in another the InkPrinter? In tests we would very much like this option.

The answer to “how can we help Spring know what to choose” is called a Configuration (Actually, a ConfigurationContext but we’ll keep it short.)

A Configuration is a set of all kinds of settings that Spring loads when it runs. Specifically, it can be a set of Beans, which are basically factories for injecting classes.

The old way to declare beans was XML-based, which was horrible to read, write and maintain. The modern way is with code. So if we want to inject a laser printer, we’ll set up a configuration class with a bean of the type Printer:

To make sure our test class uses this configuration we can tell it with the ConfigurationContext annotation:

When Spring loads the test class, it notices that Printer needs an injection. It looks into the specified PrinterConfiguration, and finds a @Bean that returns a Printer type. It then invokes the laserPrinter method in the configuration and puts the resulting object right there in the printer variable. Presto: An object was born.

There can be more than one

Configuration classes can come separate or nested, and can be combined in different ways. We can use the @Import annotation to include other configuration classes, . It is important to understand that the configurations are combined, they do not overwrite each other. That gives us better flexibility in separating configurations into manageable pieces. But that also means that they need to be managed like any code.

I know what you’re thinking: that’s ok to override the default injection, but what about the multiple implemenetors issue?

There are different ways to do this:

  • Use different configuration classes,each containing a single Bean for that type. Each test class will load the configuration it needs, not everything else.
  • Using @Primary on the bean we want to load, makes it the default in the configuration. This is great for testing if the default is declared in the application configuration, and we need to override it.
  • We can name a bean using @Bean(name=”beanName”) and then specify the bean to load with:@Autowired @Qualifier(“myBean”) in the injected classes

Working with configurations is not only a powerful way to, well, configure runs, but also a requirement in testing. Without configuration classes, we won’t be able to create different beans for different scenarios.

Sometimes we need a mock and sometimes a real object for the same type, the only other options is to create these objects ourselves in the test classes, which is missing the point in Spring. Plus, the code would be different – if we’re in charge of object creation, we need to pass around dependencies to the tested code. We’ll need different constructors or setters to pass these around. Basically we’re giving up on all the goodness Spring gets us.

Configuration classes help us solve this problem in tests, and we’ll see examples in the next posts.

 

Integration Testing With Spring – Dependency Injection

Gil Zilberfeld explains Spring testing features starting with dependency injection
Standard

In this series we're taking at Spring and its features supporting testing in general, and specifically integration tests.
Dependency injectionConfigurationsNested configurationsOrganizing configurations
Primary beansAvoiding problemsProfilesMocking I - Lifecycle
Mocking II - ResetMocking III - MockBeanData I - @SQLData II - JDBC

Data III - JPA
Controllers IControllers IIConsumer tests

I want to take a step back and discuss what Spring and Spring Boot are what are the basics features we get out of the box, and how we can use them for our advantage. There’s a whole training I’ve developed for Spring features for integration testing I do, so if you’re interested, ping me. We’ll get back to TDD-ing the hell out of it soon.

Spring started out as a dependency injection framework. “Dependency injection” is really a fancy name for something really simple, but valuable.
Let’s look at at this code of a PrinterManager class:

It’s obvious that our code is tied (or coupled) to the LaserPrinter class. What will happen if no laser printer is available? What if all we have is a whole garage full of dot matrix printers? Our code doesn’t help us there. So let’s change it to support any printer:

Holy Parameter Batman!

We now support multiple printers. I’ve used dependency injection in a way we call “passing a parameter”. Dependencies can be injected as parameters, with setter functions, or in Spring’s case, using annotations.

There’s a subtle thing we did here: We’ve separated the creation of the object (our LaserPrinter), from the use of it (inside our print method). The advantage we got is the ability to write code that doesn’t care which printer it operates.

But we can do a bit more. We may have a garage with ample supply of dot matrix printers, but a laser printer – we only got one. Now imagine that anyone created its own instance of the LaserPrinter object. Chaos ensues, but with the Printer as injected parameter the user code doesn’t know and doesn’t care.

Through the separation of creating the object and using it, we can now manage the created objects somewhere else, and without constraints from the users. We can create a LaserPrinter as a singleton, while we can create a DotMatrixPrinter as a multi-instance object, or even run a pool for clients to use. We can also do some security checks, without the client code knowing about it. For tests, we can inject mocks. Ah, the things we could do…

Wait, this is what Spring does. Spring default way of injecting objects is using the @Autowired annotation. If you see code that looks like this:

That means that when our PrinterManager loads, Spring identifies it has a dependency of type Printer, and it looks for objects to create from that type. All the PrinterManager needs to declare is the dependency type.

Spring’s advantage over passing a parameter is significant – regardless which calls I write, if it needs a Printer reference – it will get it, regardless where it is in the object chain. The alternative is pass parameters around from object to object until the dependency reaches its destination. That requires more code, more noise, and more testing. Ah it comes with more bugs.

Obviously this type of capability is a great for testability. We can’t inject any type of object we want, real ones, mocks – whatever we want – without changing the tested code.

Now the question is how does Spring know what to inject? What if Printer is an interface or an abstract class? or if there are multiple implementors of Printer? And how do we choose and tell Spring what we really really want?

We’ll discuss this next.

Refactoring Kung-Fu, Part VI

Gil Zilberfeld explains refactoring in legacy code, when using ApprovalTests and something unexpected happens
Standard

In this series we're taking a look at how to refactor un-refactorable legacy code.
Part IPart IPart III
Part IVPart V

Part VI


Ok, time to run the test.

Well, that’s interesting. The test is not even running until the end, we’re crashing before it completes. Let’s see what’s wrong.

Seems that this line breaks:

when running the case where dish includes: Sauce = Pesto, Pasta = Ravioly. In this case, the sublist method is invoked with (1,0) which causes the IllegalArgumentException. Turns out sublist‘s fromIndex (the first parameter) should be smaller than the toIndex (the second). I bet that never happens in legacy code.

What happens now? We’ve clearly identified a case where the code breaks. It is one of these “how did it ever work” moments. What do we do?

Let’s investigate a bit more. Let’s run each case separately to see which cases cause the crash, and which just fail the approval. Is this a big problem, or a single case problem?

Turns out it just this case, all other cases don’t crash. What do we do? Well, we’ve identified a case that breaks the system, and we still don’t know enough about the system to create a test for it.

We can rely on the “how did it ever work” feeling, and therefore think: This case doesn’t occur in the wild, (otherwise we would have known about it), there’s not much sense in keeping it in check. As part of an investigation in the real world legacy code, I would try to reproduce the breaking case in the system, or at least understand why it hasn’t occurred yet.

What do we do?

We can disregard that case, and not use it as a characterization of the system. All other cases don’t cause breakage, and we can work with them.

And yet, it doesn’t feel right. The behavior exists in the code, and it has some kind of effect (the breaking kind). Maybe we want to make sure that although this is a breaking behavior, it is still a behavior we want to maintain. In that case, we can modify the test to something like this:

This way, we also log the error as part of the process, and in our refactoring preserve the behavior.

We can also modify the PastaMaker code to not break (swallow the exception, fix it ad-hoc, put the offending line inside a try-catch block and handle it there, or any other creative method). While this is a valid option, it is risky, because what we’re actually doing is not refactoring, we’re changing functionality without safeguards.

The option I chose is to disregard the breaking case for now, and add a test for it later. I’m planning to come back to it and make sure it is covered, once I know more about it.

Also, if we leave the approval tests as part of the CI, I’m not sure I want to preserve an exception throwing behavior. While it is the current behavior, I don’t intend to leave it like that eventually, which goes back to writing that test later.

So for now, I’m commenting out the last line of the dish list:

Next: Explore the output.

Refactoring Kung-Fu, Part V

Standard

In this series we're taking a look at how to refactor un-refactorable legacy code.
Part IPart IPart III
Part IVPart V

Part VI


Last time we talked about flow analysis in the code. We figured out the cases we want covered. But, even for these cases, we don’t know what the expected result is.

It’s worse than that in the real world. In our case, after we’ve drawn the boundary and decided which code stays and which waits around the corner, we remain with code that seem to have no known side effects.

But, what if our code, within the boundary, is more complex, and relies on other frameworks or libraries? It could be that there are side effects we don’t see directly from the code (like the calling method scenario from last time).

Ideally, in the real scenario, we would want to log everything that the system does, and from that, deduct what our asserts should be. Luckily, there’s a tool for that.

Enter ApprovalTests

Remember we talked about characterization tests? Those that we run in order to know what the system does, and then make those our asserts. ApprovalTests does that, and even creates those asserts. Kinda.

The way ApprovalTests works, is we write a couple of tests. When we run them, it creates two text files: The “received” file, and the “approved” file. The “approved” file becomes our golden template. Every time we run the test, the regenerated “received” file is compared to the “approved” file. If they are completely the same, then at least we know that the tests run as before (to the best of our knowledge). If the files are different, we’ve broken something. Excellent for refactoring.

But what do these files actually contain? Well, everything we want to write. ApprovalTests ties into the standard IO stream, so whatever we write in the tests and in the code goes there. The more we write there, the better our comparison between changes becomes. If however, we write just a bit, we may know that this bit hasn’t changed, but not a lot about other cases.

Once we’re done with out refactoring, we can either throw away the tests, or keep them, along with the “approved” version in our CI. Not ideal, but a lot more than no tests.

You can read more about it on the ApprovalTests site, of course. For now, I’m assuming you have some familiarity with how it works, and continue from here.

Let’s check out the test

If you look at our tests folder, you’ll see a single test, an ApprovalTests test. I’m using the @DiffReporter  annotation, so if anything is different between the “received” and “approved” files, it triggers my diff tool to run.

Also you can see on our test, there’s no Asserts of any kind, but a simple:

Which basically means nothing. For now.

Now, if we want to cover our code with tests (or ApprovalTests), we need to collect a lot more logging. We’ve already covered what cases we need to track, and we’ve modified the code, in order to easily add those writing.

Next step? Add a couple of mocks. Remember the Dispenser interface? Let’s create a mock dispenser. We can use a mocking framework, but it’s much easier to create a manual mock. Since only the test uses it, I’ll create it in the test folder. Here it is:

As you can see from the implementation, whenever a method on the mock gets called, it adds the information to the log. And I’ve added a nice  toString()  override for getting the log. Since the Dispenser interface is our boundary, we’d like to know everything that goes through it. I’m not doing this now, but I’d also log what I’m returning if I think it makes sense.

Note that logging doesn’t have to be concentrated in the mocks. You can also spread all kinds of logging in the code itself. Then collect those later into the test.

Now that we have logging going on, let’s write a real test. Remember the whole test case analysis? Here’s how the test looks like, and it runs many cases (almost like a parametric test):

All the dishes are cases we’ve identified and want to run. And here’s the Dish class (again, in the test folder, it’s not part of the production code. Maybe in the future):

As you can see I want the dishes to also add themselves to the log, so now I know what goes into the Pastamaker, in addition to what goes out.

Next time: Running the test and exploring the results. It’s going to be a bumpy ride.