-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Swap/Change SystemContext objects with mocks #20
Comments
Yes, that does sounds like a very useful idea. It might be hard to implement given the controller instance is already wired? I hope to be able to look at it tomorrow night. |
Note: The existing designed approach for doing something like this is: @Test
public void myComponentTest() {
// we have some test doubles we want to use
MyRedisApi mockRedis = mock(MyRedisApi.class);
MyDatabaseApi mockDb = mock(MyDatabaseApi.class);
// create the context programmatically with some
// test doubles rather than the real dependencies
try (BeanContext context = new BootContext()
.withBeans(mockRedis, mockDb)
.load()) {
// perform a component test (using the test doubles)
CoffeeMaker coffeeMaker = context.getBean(CoffeeMaker.class);
coffeeMaker.brew();
}
} The implication for the above selenium integration test would be ... we'd wire up a whole Javalin instance that used the mocked controllers. That is, the |
Hi @rbygrave Thx, I wasn't aware of this. While trying to implement I encountered a Problem. Mockito version is 1.10.19 (latest stable) HtmlController hc = Mockito.mock(HtmlController.class);
System.out.println(hc); // <--- not null
try (BeanContext context = new BootContext().withBeans(hc).load()) {
HtmlController htmlController = context.getBean(HtmlController.class);
Security securityController = context.getBean(Security.class);
System.out.println(htmlController); //<--- prints null
System.out.println(securityController);
} |
Ok, my bad. My use of this has been with test doubles that match the class. That is: HtmlController hc = Mockito.mock(HtmlController.class);
// false because mockito generates an interesting class here
boolean match = HtmlController.class.equals(hc.getClass()); That is ... instead of HtmlController hc = Mockito.mock(HtmlController.class);
new BootContext()
.withBean(HtmlController.class, hc) // the class for injection + the mock instance
.load() This |
So fixing this in version 1.5 by adding support for @Test
public void withMockitoMock_expect_mockUsed() {
Pump mock = Mockito.mock(Pump.class);
try (BeanContext context = new BootContext()
.withBean(Pump.class, mock)
.load()) {
Pump pump = context.getBean(Pump.class);
assertThat(pump).isSameAs(mock);
CoffeeMaker coffeeMaker = context.getBean(CoffeeMaker.class);
assertThat(coffeeMaker).isNotNull();
}
} |
Just released 1.5 to maven central ... so you should be able to try that and confirm that works as you'd hope by using: HtmlController hc = Mockito.mock(HtmlController.class);
...
new BootContext()
.withBean(HtmlController.class, hc) // the class for injection + the mock instance
.load()
... |
Hi @rbygrave please forget my last comment, I was just plain stupid. But there is a Problem when using: ...
new BootContext()
.withBean(EmailService.class, emailServiceMock) // the class for injection + the mock instance
.load()
... When the mocked bean itself is a singleton the injected dependencies are null :-( lets say: EmailService emailServiceMock = spy(EmailService.class);
doNothing().when(emailServiceMock).sendEmail(any(), any(), any());
BeanContext beanContext = new BootContext()
.withBean(EmailService.class, emailServiceMock)
.load() Now the beanContext uses the mock, but the mock itself has no injected objects: @Singleton
public class EmailService {
@Inject public ContentService contentService; //<-- this is null!
... |
That is the expected behaviour in that ... we (code) is providing the emailServiceMock instance rather than DInject. I'm not expecting mocks to also have dependency injection or at least dependency injection applied to them. Hmmm. |
but without it, you can't mock controllers, services, repositories that use dependency injection... Maybe a parameter to the withBean method would be alternative? context.withBean(EmailService.class, emailServiceMock, true); |
Maybe your thinking about pure mocks - then it makes less sense to use dependency injection. But if you are using spyies, methods that are not mocked should work as expected. Here is an example: BootContext bootContext = new BootContext();
EmailService emailServiceSpy = spy(EmailService.class);
doNothing().when(emailServiceSpy).sendEmail(anyString(), anyString(), anyString());
bootContext.withBean(EmailService.class, emailServiceSpy);
beanContext = bootContext.load(); In this Spy only the sendEmail method is "mocked" to prevent unwanted emails. |
A little addition: I came accros this while migrating from spring there I use: ...
@SpyBean private EmailService emailService;
@SpyBean private VehicleService vehicleService;
@Before
public void setUp() throws Exception {
doNothing().when(vehicleService).refresh(any());
doNothing().when(emailService).sendEmail(any(), any(), any());
}
... The spyied beans all have there dependencies injected... For Dinject, I would recomend the parameter option mentioned above OR if you are having strong conviction about this :-), Dinject could provide a method to "manually" do the injections - context.loadSingle(EmailService.class, emailServiceSpy) |
Right. So I'm having thoughts around ... DBuilder, builder.isAddBeanFor() and builder.register() if (builder.isAddBeanFor(..., ...)) {
Foo bean = ...
builder.register(bean, null, ...);
} ... which gives us So the question would be should we have an alternate DBuilder that can do things like wrap bean with spy etc. A DBuilder we would probably only use in tests. |
Returning false on |
So for spy @Test
public void withMockitoSpy_expect_spyUsed() {
try (BeanContext context = new BootContext()
.withSpy(Pump.class, pump -> {
// do something interesting to setup the spy
doNothing().when(pump).pumpWater();
})
.load()) {
// Returns the mockito enhanced spy pump instance
Pump pump = context.getBean(Pump.class);
CoffeeMaker coffeeMaker = context.getBean(CoffeeMaker.class);
assertThat(coffeeMaker).isNotNull();
coffeeMaker.makeIt();
// and we can spy verify
verify(pump).pumpWater();
}
} and for mock @Test
public void withMockitoMock_expect_mockUsed() {
AtomicReference<Pump> mock = new AtomicReference<>();
try (BeanContext context = new BootContext()
.withMock(Grinder.class)
.withMock(Pump.class, pump -> {
// do something interesting to setup the mock
mock.set(pump);
})
.load()) {
Pump pump = context.getBean(Pump.class);
assertThat(pump).isSameAs(mock.get());
CoffeeMaker coffeeMaker = context.getBean(CoffeeMaker.class);
assertThat(coffeeMaker).isNotNull();
}
} |
SPY - 3 setup optionsNo setup @Test
public void withMockitoSpy_noSetup_expect_spyUsed() {
try (BeanContext context = new BootContext()
.withSpy(Pump.class)
.load()) {
CoffeeMaker coffeeMaker = context.getBean(CoffeeMaker.class);
assertThat(coffeeMaker).isNotNull();
coffeeMaker.makeIt();
Pump pump = context.getBean(Pump.class);
verify(pump).pumpWater();
}
} Setup after beanContext.load() @Test
public void withMockitoSpy_postLoadSetup_expect_spyUsed() {
try (BeanContext context = new BootContext()
.withSpy(Pump.class)
.load()) {
// setup after load()
Pump pump = context.getBean(Pump.class);
doNothing().when(pump).pumpWater();
CoffeeMaker coffeeMaker = context.getBean(CoffeeMaker.class);
assertThat(coffeeMaker).isNotNull();
coffeeMaker.makeIt();
verify(pump).pumpWater();
}
} Setup inline ... @Test
public void withMockitoSpy_expect_spyUsed() {
try (BeanContext context = new BootContext()
.withSpy(Pump.class, pump -> {
// setup the spy
doNothing().when(pump).pumpWater();
})
.load()) {
Pump pump = context.getBean(Pump.class);
CoffeeMaker coffeeMaker = context.getBean(CoffeeMaker.class);
assertThat(coffeeMaker).isNotNull();
coffeeMaker.makeIt();
verify(pump).pumpWater();
}
} |
This is enhancement of the instance with dependencies injected and then registering/using that enhanced instance
This is enhancement of the instance with dependencies injected and then registering/using that enhanced instance
See #22 |
Provides a cleaner separation for handling mocks (supplied beans) and spy (enhancement)
Adding support for Mockito spy (#20) …
Right, merged in #22 ... releasing at version 1.6. We can close this for the moment and you can try version 1.6 and confirm that works as desired. Cheers, Rob. |
O wow, that was fast again. @rbygrave thank you very much, with this I can finaly migrate completly from spring. Ive spend some time to write a nice Junit5 extension, its small and fully configurable. If you are interested I would love to share it - maybe you can use it or enhance on it. |
@yaskor ... no problem - thanks for raising the issue, it's nice to have it sorted. I'm pretty happy with how the internals improved and we have a nicer API for mocks and spy's now so win win - I'm really happy with the result !!
Yeah that would be great. I haven't had a chance to even use Junit5 yet - crazy !! |
It would be awesome if dinject could swap objects for a test and reverert to the real object afterwards.
In my case Im trying to write Selenium usecase tests. The javalin server is allready started with all its controllers (dinject controlled singletons), but at some point I want that some controllers behave different.
This is the JUnit5 extension witch starts the server:
And this is the main method called in the extension
The text was updated successfully, but these errors were encountered: