Working with just one screen

I'm a software developer. And I use just one monitor for work.

Jap, for some SW developers this is something absolutely unimaginable.

How? There's one shortcut that keeps me away from multi-screen setup:

super + number

It opens (or switches focus to) an application from taskbar (based on order). For example super + 1 will always get me to my browser as it's my first taskbar app. This is the ordering I'm used to:

  1. Firefox browser
  2. Console terminal
  3. Atom text editor
  4. File explorer
  5. IDE
  6. Instant messenger (at work), Steam (at home) - ehm, consistency, right
  7. KeePassX

It has following benefits:

  • It's super easy to switch focus to a different application. - It becomes just a muscle memory.
  • It works out of a box in operating systems I use (Windows, Ubuntu).
  • There's just one place where any application with gained focus will pop up - right in front of my eyes.
  • The setup and working conditions are exactly the same no matter if I have a laptop, PC, or laptop with external monitor (I would disable laptop's screen in this case to limit distractions).

I guess this will not fit everybody and there might be use cases where one screen is not enough - but either I don't encounter them or there are easy to overcome.

3 tips for using Spring Boot test slices

Introduction

Spring Boot test slices are a great way to test your services with Spring Boot beans and configuration. They will load only a necessary part of framework for you and thus making integration tests:

  • faster,
  • easier to reason about,
  • and less fragile (as they will not break on changes in unrelated configuration / beans).

Some time back I've written an article about making DataJpaTest to catch more bugs but let's have a look now at some general tips on using all types of slices:

Tip 1: How to resolve dependencies of a tested service

Many times a test with a slice will need additional dependencies. There are a few ways how to load them to the test context:

  • @MockBean - creates a mockito instance for annotated field. Do not confuse this annotation with @Mock from Mockito library.
  • @Import - loads a service/configuration into the test context.
  • @AutoConfigure* annotations - autoconfigures more beans from SpringBoot application; for example @AutoConfigureJson will configure Jackson's ObjectMapper. - You should have only one slice annotation on a test class but you can add more autoconfigurations if necessary.

Let's have a look at an example with a CreateUserService depending on JPA repositories, Jackson ObjectMapper and some custom classes for generating passwords / sending emails:

@DataJpaTest
@AutoConfigureJson
@Import({
    CreateUserService.class,
    RandomPasswordGenerator.class
})
class CreateUserServiceTest {
    @MockBean
    private EmailSender emailSender;

    @Autowired
    private CreateUserService createUserService;

    // ...
}

Tip 2: Make sure SpringBootApplication does not have unnecessary configuration

Your main application class should ideally look like this:

@SpringBootApplication(
    exclude = {/* any autoconfiguration you want to disable */}
)
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Any additional configuration would be loaded by all slices too and so it should go to separate code>@Configuration classes if possible. (See docs for configuration and slices.)

Be especially aware of code>@ComponentScan annotation - do not forget to put a TypeExcludeFilter into it otherwise your slices will start loading all configurations and components from specified packages. The TypeExcludeFilter is one of classes responsible for making slices work.

Tip 3: You can extend existing slices

Do you need additional beans for every single WebMvcTest (or any other test slice)? Simply extend the existing slice and import/mock your dependencies:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@WebMvcTest
@Import(MandatoryDependency.class)
public @interface CustomWebMvcTest {
    @AliasFor(annotation = WebMvcTest.class, attribute = "controllers")
    Class<?>[] controllers() default {};
}

Notice that you can also "forward" fields of the new slice to the original slice using code>@AliasFor annotation.

Final thoughts

I guess this goes without saying - but checking documentation, or implementation of slices can show you even more possibilities. For example DataJpaTest automatically configures TestEntityManager, WebMvcTest can load only one controller using controllers attribute, JsonTest provides JacksonTester class for easy asserting of JSON content and so on...

There could be a lot written about how to use any particular slice but hopefully these simple tips will help you to use them better.

About not so funny programming jokes

Please throw this back at me whenever I start a rant about a framework or language. 🙂

Every now and then I see a joke about basically any technology that managed to be somewhat popular and I'm like - oh boy, here it comes again.


Full comic strip: https://imgur.com/mCX48bp

Imagine a doctor saying: "Oh, damn, this tool tricked me again! I though it will clear your tooth from mess but it's just making holes. Damn thing, they should have painted it red, lazy manufacturers..."

Would you go visit this doctor again? Or would you run away in terror (or in search for a lawyer). It really feels the same to me in software development as it's somehow acceptable to know our tools just enough to get a project done.

I'm not saying we should use every single library only after reading their reference first. But when our expectations fail we should go back to where our careers started: Learning how our tools work instead of blaming them. Programming with our intuition and using concepts we already know is great but it can go only that far.

Basically every time I had those strong feelings (and comments :]) about some technology I have found out later that I just didn't know enough. All while my more experienced coworkers did imaginary facepalms for sure.

It's way more interesting to find out how things actually work and when to [not] use them. Follow that I'm sure it will go a long way for your career. I cannot emphasize enough how rewarding is getting through issues some people would cast aside as unpredictable magic. The reality is - those technologies we use every day are popular for a reason and they will stuck with us for quite some time - better to learn them now.

Spring Boot’s DataJpaTest: Do you flush and clear?

Intro

First of all - DataJpaTest is a great helper from the Spring Boot framework. If you are not familiar with this class then please go ahead to read a tutorial on it and then come back! In this article I want to show how to get more of its potential.

But in short - DataJpaTest automatically configures Hibernate with in-memory database (and all Spring beans that go with it). It will also run your tests in transactions so you don't need to remove created entities manually.

The issue with plain DataJpaTest

So - we have DataJpaTest and can focus on testing the behavior of our services - sounds great. But what are the implications of this setup? Let's have a look at this test:

(Please be aware that everything before assertions is business logic which would be hidden in some service - for the sake of readability I've put this code directly into the test itself.)

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "name")
    private String name;

    @OneToMany(mappedBy = "author")
    private List<Article> articles = new ArrayList<>();

    // ...plus constructors, getters and setters
}

@Entity
public class Article {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private User author;

    @Column(name = "content")
    private String content;

    // ...plus constructors, getters and setters
}

@DataJpaTest
class BlogTests {
    @Autowired
    private UserRepository userRepository;

    @Test
    public void givenUserAndArticle_Save_ShouldPersistBothToDatabase() {
        // some business logic;
        // it would be inside a service usually
        Article article = new Article("some content");

        User user = new User("foo");
        user.addArticle(article);
        user = userRepository.save(user);
        // the end of business logic

        assertThat(user.getArticles()).hasSize(1);
    }
}

Looks good, right? Unfortunately after testing the code in some real environment we find out the article is actually never saved. So let's tune our logging to see what's going on:

@DataJpaTest(properties = {
    "logging.level.ROOT= WARN",
    "logging.level.org.springframework.test.context.transaction= INFO",
    "logging.level.org.hibernate.SQL= DEBUG"
})

And here is the output:

2020-01-19 17:45:16.563  INFO 1844 --- [           main] o.s.t.c.transaction.TransactionContext   : Began transaction (1) for test context [DefaultTestContext@21b2e768 testClass = BlogTests, ...]
2020-01-19 17:45:16.630 DEBUG 1844 --- [           main] org.hibernate.SQL                        : call next value for hibernate_sequence
Hibernate: call next value for hibernate_sequence
2020-01-19 17:45:16.733  INFO 1844 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@21b2e768 testClass = BlogTests, ...]

We can see the transaction started and rollbacked but our insert query is missing. It's because the save method does not guarantee the insert will happen immediately when run inside a transaction. In our case the insert is delayed and right after save there is a rollback from DataJpaTest - so there's no need to do DB inserts.

Furthermore we are asserting properties on the entity instance coming from the block with business logic - but we can (and IMHO should) try to fetch it again and see if eveything was saved.

The solution

So let's make the test fail:

@Autowired
private UserRepository userRepository;

@Autowired
private TestEntityManager testEntityManager;

@Test
public void givenUserAndArticle_Save_ShouldPersistBothToDatabase() {
    // some business logic;
    // it would be inside a service usually
    Article article = new Article("some content");

    User user = new User("foo");
    user.addArticle(article);
    user = userRepository.save(user);
    // end of business logic

    // all good here
    assertThat(user.getArticles()).hasSize(1);

    // forces synchronization to DB
    testEntityManager.flush();
    // clears persistence context
    // all entities are now detached and can be fetched again
    testEntityManager.clear();

    Optional<User> fetchedUser = userRepository.findById(user.getId());

    assertThat(fetchedUser).isPresent();
    // the article must be saved manually
    // OR User#articles should have a proper CascadeType
    assertThat(fetchedUser.get().getArticles())
        .withFailMessage("the article was never persisted")
        .hasSize(1);
}

We use flush to force synchronization (all delayed SQL queries will be done) and the clear method to allow fetching entities again. After that we fetch User entity from database and check that all entities are the way they should be.

And that's it. With this approach you can catch misconfigurations of JPA entities (wrong CascadeType, missing MappedSuperclass), bugged business logic (accidentally saving two entities with the same unique key; data not passing validation constraints) and more. The test is not perfect - because quite frankly all tests are a compromise of speed, simplicity, scope and type of bugs you will catch - but with little effort you can make it a bit more robust.

Thanks for reading this far and share your thougts in the comments!

(Code from this article can be downloaded from my Github - feel free to have a look and play with it.)


Did you like the article? Feel free to share it with your colleagues. To get nofications about more blog posts: follow me on Twitter or subscribe to RSS of blog posts.