3

I have any issue in my unit test where I have something along the lines of this. The mock injection get overridden on the someService if the blargh function is annotated with Transactional. If I remove the Transactional the mock stays there. From watching the code it appears that Spring lazily loads the services when a function in the service is annotated with transactinal, but eagerly loads the services when it isn't. This overrides the mock I injected.

Is there a better way to do this?

@Component
public class SomeTests
{
  @Autowired
  private SomeService someService;

  @Test
  @Transactional
  public void test(){
    FooBar fooBarMock = mock(FooBar.class);
    ReflectionTestUtils.setField(someService, "fooBar", fooBarMock);
  }
}

@Service
public class someService
{
  @Autowired FooBar foobar;

  @Transactional // <-- this causes the mocked item to be overridden
  public void blargh()
  {
    fooBar.doStuff();
  }
}
Zipper
  • 7,034
  • 8
  • 49
  • 66

3 Answers3

2

Probably you could try to implement your test in the following way:

@Component
@RunWith(MockitoJUnitRunner.class)
public class SomeTests
{
  @Mock private FooBar foobar;
  @InjectMocks private final SomeService someService = new SomeService();


  @Test
  @Transactional
  public void test(){
    when(fooBar.doStuff()).then....;
    someService.blargh() .....
  }
}

I could not try it right now as don't have your config and related code. But this is one of the common way to test the service logic.

shippi
  • 2,344
  • 7
  • 22
  • 28
  • Of course it requires Mockito but it keeps your tests simple and clean. – shippi Jan 14 '14 at 21:47
  • This would work, but for the tests I have sometimes I need the mock injected, and other times I don't. It depends on what the test is testing. Worse case is I can do this and just separate the tests that need this in to their own class, but I was hoping to keep these tests grouped together. – Zipper Jan 15 '14 at 02:19
  • Okay I am with you now! Probably i would keep those test separately and would have a suite of regression tests and a suite of such kind of integration ones. Dunno what is your use case to have real objects injected instead of mocks. I am trying to always focus to test just the bean I created the jUnit for and everything else is mocked. (and tested on another layer). But as I said I dont know your use case. – shippi Jan 15 '14 at 08:19
  • tried this today, using the @InjectMocks, but it appears to have the same issue, the mock is over-written when it lazily loads the rest of the services. – Zipper Jan 15 '14 at 14:15
  • Using the @Transactional itself should not cause the lazy loading of the beans,therefore they are injected after the bean is created. The only difference could be that the transactional beans are proxied if not CGLIb is used for the transactional processing. I would create a very simple project with one spring bean and one test and lets see whether you have the same issue.If it would work and as you said that you want to have a mocked version and a non mocked version you could probably hold two references of the service in the test. MockedService and nonMockedService.But have no clever idea – shippi Jan 15 '14 at 19:55
0

Use the Spring @Profile functionality - beans can be associated to a certain group, and the group can be activated or deactivated via annotations.

Check this blog post and the documentation for more detailed instructions, this is an example of how to define production services and two groups of mock services:

@Configuration
@Profile("production")
public static class ProductionConfig {
    @Bean
    public InvoiceService realInvoiceService() {
        ...
    }
    ...
}

@Configuration
@Profile("testServices")
public static class TestConfiguration {
    @Bean
    public InvoiceService mockedInvoiceService() {
        ...
    }
    ...
}

@Configuration
@Profile("otherTestServices")
public static class OtherTestConfiguration {
    @Bean
    public InvoiceService otherMockedInvoiceService() {
        ...
    }
    ...
}

And this is how to use them in the tests:

@ActiveProfiles("testServices")
public class MyTest extends SpringContextTestCase {
    @Autowired
    private MyService mockedService;

    // ...
}

@ActiveProfiles("otherTestServices")
public class MyOtherTest extends SpringContextTestCase {
    @Autowired
    private MyService myOtherMockedService;

    // ...
}
Angular University
  • 42,341
  • 15
  • 74
  • 81
0

I have the exact same problem and I solve it by using Mockito.any() for the arguments

eg:

when(transactionalService.validateProduct(id)).thenReturn("")
=> when(transactionalService.validateProduct(Mockito.any())).thenReturn("")
infD
  • 43
  • 7